mirror of
https://github.com/drogonframework/drogon.git
synced 2025-08-29 00:02:44 -04:00
Drogon test framework (#869)
This commit is contained in:
parent
afb7e853ec
commit
0b5075bfa9
@ -1,4 +1,4 @@
|
||||
version: 1.2.0.{build}
|
||||
version: 1.6.0.{build}
|
||||
configuration:
|
||||
- Release
|
||||
- Debug
|
||||
@ -38,4 +38,4 @@ build:
|
||||
project: build\ALL_BUILD.vcxproj
|
||||
verbosity: minimal
|
||||
cache:
|
||||
- c:\tools\vcpkg\installed\
|
||||
- c:\tools\vcpkg\installed\
|
||||
|
9
.github/workflows/cmake.yml
vendored
9
.github/workflows/cmake.yml
vendored
@ -127,15 +127,6 @@ jobs:
|
||||
run: |
|
||||
sudo apt install postgresql-all
|
||||
|
||||
- name: install gtest
|
||||
run: |
|
||||
wget https://github.com/google/googletest/archive/release-1.10.0.tar.gz
|
||||
tar xf release-1.10.0.tar.gz
|
||||
cd googletest-release-1.10.0
|
||||
cmake .
|
||||
make
|
||||
sudo make install
|
||||
|
||||
- name: Create build directory
|
||||
run: |
|
||||
mkdir build
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -45,4 +45,5 @@ latex/
|
||||
.vs/
|
||||
CMakeSettings.json
|
||||
install
|
||||
trace.json
|
||||
trace.json
|
||||
.cache
|
20
CMakeLists.txt
Executable file → Normal file
20
CMakeLists.txt
Executable file → Normal file
@ -40,6 +40,14 @@ set(DEF_INSTALL_DROGON_CMAKE_DIR lib/cmake/Drogon)
|
||||
set(INSTALL_DROGON_CMAKE_DIR ${DEF_INSTALL_DROGON_CMAKE_DIR}
|
||||
CACHE PATH "Installation directory for cmake files")
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
|
||||
# Force MSVC to use UTF-8 because that's what we use. Otherwise it uses
|
||||
# the default of whatever Windows sets and causes encoding issues.
|
||||
message(STATUS "You are using MSVC. Forceing to use UTF-8")
|
||||
add_compile_options("$<$<C_COMPILER_ID:MSVC>:/utf-8>")
|
||||
add_compile_options("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||
endif ()
|
||||
|
||||
if (BUILD_DROGON_SHARED)
|
||||
set(BUILD_TRANTOR_SHARED TRUE)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
|
||||
@ -77,6 +85,7 @@ include(GenerateExportHeader)
|
||||
generate_export_header(${PROJECT_NAME} EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/exports/drogon/exports.h)
|
||||
|
||||
include(cmake/DrogonUtilities.cmake)
|
||||
include(cmake/ParseAndAddDrogonTests.cmake)
|
||||
include(CheckIncludeFileCXX)
|
||||
|
||||
check_include_file_cxx(any HAS_ANY)
|
||||
@ -415,6 +424,8 @@ configure_file(${PROJECT_SOURCE_DIR}/cmake/templates/config.h.in
|
||||
${PROJECT_BINARY_DIR}/drogon/config.h @ONLY)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
message(STATUS "Building tests")
|
||||
enable_testing()
|
||||
add_subdirectory(lib/tests)
|
||||
if (pg_FOUND)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/postgresql_impl/test)
|
||||
@ -426,12 +437,6 @@ if (BUILD_TESTING)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/src/sqlite3_impl/test)
|
||||
endif (SQLite3_FOUND)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/orm_lib/tests)
|
||||
find_package(GTest)
|
||||
if (GTest_FOUND)
|
||||
message(STATUS "gtest found")
|
||||
enable_testing()
|
||||
add_subdirectory(unittest)
|
||||
endif (GTest_FOUND)
|
||||
endif (BUILD_TESTING)
|
||||
|
||||
# Installation
|
||||
@ -474,6 +479,7 @@ set(DROGON_HEADERS
|
||||
lib/inc/drogon/version.h
|
||||
lib/inc/drogon/drogon_callbacks.h
|
||||
lib/inc/drogon/PubSubService.h
|
||||
lib/inc/drogon/drogon_test.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/exports/drogon/exports.h)
|
||||
install(FILES ${DROGON_HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/drogon)
|
||||
|
||||
@ -556,6 +562,7 @@ install(FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/Findcoz-profiler.cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_modules/FindHiredis.cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/DrogonUtilities.cmake"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/ParseAndAddDrogonTests.cmake"
|
||||
DESTINATION "${INSTALL_DROGON_CMAKE_DIR}"
|
||||
COMPONENT dev)
|
||||
|
||||
@ -564,3 +571,4 @@ install(EXPORT DrogonTargets
|
||||
DESTINATION "${INSTALL_DROGON_CMAKE_DIR}"
|
||||
NAMESPACE Drogon::
|
||||
COMPONENT dev)
|
||||
|
||||
|
79
cmake/ParseAndAddDrogonTests.cmake
Normal file
79
cmake/ParseAndAddDrogonTests.cmake
Normal file
@ -0,0 +1,79 @@
|
||||
#==================================================================================================#
|
||||
# Adapted and re-written from Catch2 to work with Drogon Test #
|
||||
# #
|
||||
# Usage #
|
||||
# 1. make sure this module is in the path or add this otherwise: #
|
||||
# set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules/") #
|
||||
# 2. make sure that you've enabled testing option for the project by the call: #
|
||||
# enable_testing() #
|
||||
# 3. add the lines to the script for testing target (sample CMakeLists.txt): #
|
||||
# project(testing_target) #
|
||||
# enable_testing() #
|
||||
# #
|
||||
# file(GLOB SOURCE_FILES "*.cpp") #
|
||||
# add_executable(${PROJECT_NAME} ${SOURCE_FILES}) #
|
||||
# #
|
||||
# include(ParseAndAddDrogonTests) #
|
||||
# ParseAndAddDrogonTests(${PROJECT_NAME}) #
|
||||
#==================================================================================================#
|
||||
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# This removes the contents between
|
||||
# - block comments (i.e. /* ... */)
|
||||
# - full line comments (i.e. // ... )
|
||||
# contents have been read into '${CppCode}'.
|
||||
# !keep partial line comments
|
||||
function(RemoveComments CppCode)
|
||||
string(ASCII 2 CMakeBeginBlockComment)
|
||||
string(ASCII 3 CMakeEndBlockComment)
|
||||
string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}")
|
||||
string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}")
|
||||
string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}")
|
||||
string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}")
|
||||
|
||||
set(${CppCode} "${${CppCode}}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Worker function
|
||||
function(ParseFile SourceFile TestTarget)
|
||||
set(FullSourcePath ${CMAKE_CURRENT_SOURCE_DIR}/${SourceFile})
|
||||
if(NOT EXISTS ${FullSourcePath})
|
||||
return()
|
||||
endif()
|
||||
file(STRINGS ${FullSourcePath} Contents NEWLINE_CONSUME)
|
||||
|
||||
# Remove block and fullline comments
|
||||
RemoveComments(Contents)
|
||||
|
||||
# Find definition of test names
|
||||
string(REGEX MATCHALL "[ \t]*DROGON_TEST[ \t]*\\\([a-zA-Z0-9_]+\\\)" Tests "${Contents}")
|
||||
|
||||
foreach(TestLine ${Tests})
|
||||
# Strip newlines
|
||||
string(REGEX REPLACE "\\\\\n|\n" "" TestLine "${TestLine}")
|
||||
|
||||
# Get the name of the test
|
||||
string(REGEX REPLACE "[ \t]*DROGON_TEST[ \t]*" "" TestLine "${TestLine}")
|
||||
string(REGEX MATCHALL "[a-zA-Z0-9_]+" TestName "${TestLine}")
|
||||
|
||||
# Validate that a test name and tags have been provided
|
||||
list(LENGTH TestName TestNameLength)
|
||||
if(NOT TestNameLength EQUAL 1)
|
||||
message(FATAL_ERROR "${TestName} in ${SourceFile} is not a valid test name."
|
||||
" Either a bug in the Drogon Test CMake parser or a bug in the test itself")
|
||||
endif()
|
||||
|
||||
# Add the test and set its properties
|
||||
add_test(NAME "${TestName}" COMMAND ${TestTarget} -r ${TestName} ${AdditionalCatchParameters})
|
||||
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# entry point
|
||||
function(ParseAndAddDrogonTests TestTarget)
|
||||
get_target_property(SourceFiles ${TestTarget} SOURCES)
|
||||
foreach(SourceFile ${SourceFiles})
|
||||
ParseFile(${SourceFile} ${TestTarget})
|
||||
endforeach()
|
||||
endfunction()
|
@ -1,80 +1,18 @@
|
||||
link_libraries(${PROJECT_NAME})
|
||||
|
||||
set(simple_example_sources
|
||||
simple_example/CustomCtrl.cc
|
||||
simple_example/CustomHeaderFilter.cc
|
||||
simple_example/DoNothingPlugin.cc
|
||||
simple_example/ForwardCtrl.cc
|
||||
simple_example/JsonTestController.cc
|
||||
simple_example/ListParaCtl.cc
|
||||
simple_example/PipeliningTest.cc
|
||||
simple_example/TestController.cc
|
||||
simple_example/TestPlugin.cc
|
||||
simple_example/TestViewCtl.cc
|
||||
simple_example/WebSocketTest.cc
|
||||
simple_example/api_Attachment.cc
|
||||
simple_example/api_v1_ApiTest.cc
|
||||
simple_example/TimeFilter.cc
|
||||
simple_example/DigestAuthFilter.cc
|
||||
simple_example/main.cc)
|
||||
|
||||
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
set(simple_example_sources ${simple_example_sources}
|
||||
simple_example/api_v1_CoroTest.cc)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
endif(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
|
||||
add_executable(webapp ${simple_example_sources})
|
||||
drogon_create_views(webapp ${CMAKE_CURRENT_SOURCE_DIR}/simple_example
|
||||
${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_dependencies(webapp drogon_ctl)
|
||||
|
||||
set(client_example_sources client_example/main.cc)
|
||||
set(benchmark_sources benchmark/BenchmarkCtrl.cc benchmark/JsonCtrl.cc
|
||||
benchmark/main.cc)
|
||||
# AUX_SOURCE_DIRECTORY(simple_example_test DIR_TEST)
|
||||
|
||||
add_executable(client ${client_example_sources})
|
||||
add_executable(client client_example/main.cc)
|
||||
add_executable(websocket_client websocket_client/WebSocketClient.cc)
|
||||
add_executable(benchmark ${benchmark_sources})
|
||||
add_executable(webapp_test simple_example_test/main.cc)
|
||||
add_executable(pipelining_test simple_example_test/HttpPipeliningTest.cc)
|
||||
add_executable(websocket_test simple_example_test/WebSocketTest.cc)
|
||||
add_executable(multiple_ws_test simple_example_test/MultipleWsTest.cc)
|
||||
|
||||
add_custom_command(
|
||||
TARGET webapp POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E
|
||||
copy_if_different
|
||||
${PROJECT_SOURCE_DIR}/config.example.json
|
||||
${PROJECT_SOURCE_DIR}/drogon.jpg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simple_example/index.html
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simple_example/index.html.gz
|
||||
${PROJECT_SOURCE_DIR}/trantor/trantor/tests/server.pem
|
||||
$<TARGET_FILE_DIR:webapp>)
|
||||
add_custom_command(
|
||||
TARGET webapp POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E
|
||||
copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/simple_example/a-directory
|
||||
$<TARGET_FILE_DIR:webapp>/a-directory)
|
||||
set(example_targets
|
||||
webapp
|
||||
webapp_test
|
||||
client
|
||||
benchmark
|
||||
pipelining_test
|
||||
websocket_test
|
||||
multiple_ws_test)
|
||||
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
add_executable(websocket_coro_test simple_example_test/WebSocketCoroTest.cc)
|
||||
set(simple_example ${simple_example} websocket_coro_test)
|
||||
endif(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
client
|
||||
websocket_client)
|
||||
|
||||
set_property(TARGET ${example_targets}
|
||||
PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD})
|
||||
set_property(TARGET ${example_targets} PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
set_property(TARGET ${example_targets} PROPERTY CXX_EXTENSIONS OFF)
|
||||
set_property(TARGET webapp PROPERTY ENABLE_EXPORTS ON)
|
||||
|
@ -4,9 +4,8 @@ The following examples can help you understand how to use Drogon:
|
||||
|
||||
1. [benchmark](https://github.com/an-tao/drogon/tree/master/examples/benchmark) - Basic benchmark example. see [wiki benchmarks](https://github.com/an-tao/drogon/wiki/13-Benchmarks)
|
||||
2. [client_example](https://github.com/an-tao/drogon/tree/master/examples/client_example/main.cc) - A client example.
|
||||
3. [simple_example](https://github.com/an-tao/drogon/tree/master/examples/simple_example) - A simple example showing how to create a web application using Drogon.
|
||||
4. [simple_example_test](https://github.com/an-tao/drogon/tree/master/examples/simple_example_test) - Some tests for the `simple_example`.
|
||||
5. [simple_reverse_proxy](https://github.com/an-tao/drogon/tree/master/examples/simple_reverse_proxy) - A Example showing how to use drogon as a http reverse proxy with a simple round robin.
|
||||
3. [simple_reverse_proxy](https://github.com/an-tao/drogon/tree/master/examples/simple_reverse_proxy) - A Example showing how to use drogon as a http reverse proxy with a simple round robin.
|
||||
4. [websocket_client](https://github.com/an-tao/drogon/tree/master/examples/websocket_client/WebSocketClient.cc) - An example on how to use the WebSocket client
|
||||
|
||||
### [TechEmpower Framework Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks) test suite
|
||||
|
||||
|
@ -1,104 +0,0 @@
|
||||
#include <drogon/HttpClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
using namespace drogon;
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
auto client = HttpClient::newHttpClient("127.0.0.1", 8848);
|
||||
client->setPipeliningDepth(64);
|
||||
int counter = -1;
|
||||
int n = 0;
|
||||
|
||||
auto request1 = HttpRequest::newHttpRequest();
|
||||
request1->setPath("/pipe");
|
||||
request1->setMethod(Head);
|
||||
|
||||
client->sendRequest(
|
||||
request1, [&counter, &n](ReqResult r, const HttpResponsePtr &resp) {
|
||||
if (r == ReqResult::Ok)
|
||||
{
|
||||
auto counterHeader = resp->getHeader("counter");
|
||||
int c = atoi(counterHeader.data());
|
||||
if (c <= counter)
|
||||
{
|
||||
LOG_ERROR << "The response was received in "
|
||||
"the wrong order!";
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
counter = c;
|
||||
++n;
|
||||
}
|
||||
if (resp->getBody().length() > 0)
|
||||
{
|
||||
LOG_ERROR << "The response has a body:" << resp->getBody();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
auto request2 = HttpRequest::newHttpRequest();
|
||||
request2->setPath("/drogon.jpg");
|
||||
client->sendRequest(request2, [](ReqResult r, const HttpResponsePtr &resp) {
|
||||
if (r == ReqResult::Ok)
|
||||
{
|
||||
if (resp->getBody().length() != 44618)
|
||||
{
|
||||
LOG_ERROR << "The response is error!";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
auto request = HttpRequest::newHttpRequest();
|
||||
request->setPath("/pipe");
|
||||
for (int i = 0; i < 19; ++i)
|
||||
{
|
||||
client->sendRequest(
|
||||
request, [&counter, &n](ReqResult r, const HttpResponsePtr &resp) {
|
||||
if (r == ReqResult::Ok)
|
||||
{
|
||||
auto counterHeader = resp->getHeader("counter");
|
||||
int c = atoi(counterHeader.data());
|
||||
if (c <= counter)
|
||||
{
|
||||
LOG_ERROR
|
||||
<< "The response was received in the wrong order!";
|
||||
exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
counter = c;
|
||||
++n;
|
||||
if (n == 20)
|
||||
{
|
||||
LOG_DEBUG << "Good!";
|
||||
app().getLoop()->quit();
|
||||
}
|
||||
}
|
||||
if (resp->getBody().length() == 0)
|
||||
{
|
||||
LOG_ERROR << "The response hasn't a body!";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
app().run();
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
#include <drogon/WebSocketClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
std::array<WebSocketClientPtr, 1000> wsClients;
|
||||
for (size_t i = 0; i < wsClients.size(); ++i)
|
||||
{
|
||||
auto &wsPtr = wsClients[i];
|
||||
wsPtr = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
|
||||
req->setPath("/chat");
|
||||
wsPtr->setMessageHandler([](const std::string &message,
|
||||
const WebSocketClientPtr &wsPtr,
|
||||
const WebSocketMessageType &type) {
|
||||
std::cout << "new message:" << message << std::endl;
|
||||
if (type == WebSocketMessageType::Pong)
|
||||
{
|
||||
std::cout << "recv a pong" << std::endl;
|
||||
}
|
||||
});
|
||||
wsPtr->setConnectionClosedHandler([](const WebSocketClientPtr &wsPtr) {
|
||||
std::cout << "ws closed!" << std::endl;
|
||||
});
|
||||
wsPtr->connectToServer(
|
||||
req,
|
||||
[](ReqResult r,
|
||||
const HttpResponsePtr &resp,
|
||||
const WebSocketClientPtr &wsPtr) {
|
||||
if (r == ReqResult::Ok)
|
||||
{
|
||||
std::cout << "ws connected!" << std::endl;
|
||||
wsPtr->getConnection()->setPingMessage("", 2s);
|
||||
wsPtr->getConnection()->send("hello!");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "ws failed!" << std::endl;
|
||||
}
|
||||
});
|
||||
}
|
||||
app().run();
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
#include <drogon/WebSocketClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
Task<> doTest(WebSocketClientPtr wsPtr, HttpRequestPtr req, bool continually)
|
||||
{
|
||||
wsPtr->setAsyncMessageHandler(
|
||||
[continually](std::string&& message,
|
||||
const WebSocketClientPtr wsPtr,
|
||||
const WebSocketMessageType type) -> Task<> {
|
||||
std::cout << "new message:" << message << std::endl;
|
||||
if (type == WebSocketMessageType::Pong)
|
||||
{
|
||||
std::cout << "recv a pong" << std::endl;
|
||||
if (!continually)
|
||||
{
|
||||
app().getLoop()->quit();
|
||||
}
|
||||
}
|
||||
co_return;
|
||||
});
|
||||
wsPtr->setAsyncConnectionClosedHandler(
|
||||
[](const WebSocketClientPtr wsPtr) -> Task<> {
|
||||
std::cout << "ws closed!" << std::endl;
|
||||
co_return;
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
auto resp = co_await wsPtr->connectToServerCoro(req);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cout << "ws failed!" << std::endl;
|
||||
if (!continually)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
std::cout << "ws connected!" << std::endl;
|
||||
wsPtr->getConnection()->setPingMessage("", 2s);
|
||||
wsPtr->getConnection()->send("hello!");
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
auto wsPtr = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/chat");
|
||||
bool continually = true;
|
||||
if (argc > 1)
|
||||
{
|
||||
if (std::string(argv[1]) == "-t")
|
||||
continually = false;
|
||||
else if (std::string(argv[1]) == "-p")
|
||||
{
|
||||
// Connect to a public web socket server.
|
||||
wsPtr =
|
||||
WebSocketClient::newWebSocketClient("wss://echo.websocket.org");
|
||||
req->setPath("/");
|
||||
}
|
||||
}
|
||||
|
||||
app().getLoop()->runAfter(5.0, [continually]() {
|
||||
if (!continually)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
app().setLogLevel(trantor::Logger::kTrace);
|
||||
|
||||
[=]() -> AsyncTask { co_await doTest(wsPtr, req, continually); }();
|
||||
|
||||
app().run();
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
#include <drogon/WebSocketClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
auto wsPtr = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/chat");
|
||||
bool continually = true;
|
||||
if (argc > 1)
|
||||
{
|
||||
if (std::string(argv[1]) == "-t")
|
||||
continually = false;
|
||||
else if (std::string(argv[1]) == "-p")
|
||||
{
|
||||
// Connect to a public web socket server.
|
||||
wsPtr =
|
||||
WebSocketClient::newWebSocketClient("wss://echo.websocket.org");
|
||||
req->setPath("/");
|
||||
}
|
||||
}
|
||||
wsPtr->setMessageHandler([continually](const std::string &message,
|
||||
const WebSocketClientPtr &wsPtr,
|
||||
const WebSocketMessageType &type) {
|
||||
std::cout << "new message:" << message << std::endl;
|
||||
if (type == WebSocketMessageType::Pong)
|
||||
{
|
||||
LOG_DEBUG << "recv a pong";
|
||||
if (!continually)
|
||||
{
|
||||
LOG_DEBUG << "quit";
|
||||
app().getLoop()->quit();
|
||||
}
|
||||
}
|
||||
});
|
||||
wsPtr->setConnectionClosedHandler([](const WebSocketClientPtr &wsPtr) {
|
||||
std::cout << "ws closed!" << std::endl;
|
||||
});
|
||||
wsPtr->connectToServer(req,
|
||||
[continually](ReqResult r,
|
||||
const HttpResponsePtr &resp,
|
||||
const WebSocketClientPtr &wsPtr) {
|
||||
if (r == ReqResult::Ok)
|
||||
{
|
||||
std::cout << "ws connected!" << std::endl;
|
||||
wsPtr->getConnection()->setPingMessage("",
|
||||
2s);
|
||||
wsPtr->getConnection()->send("hello!");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "ws failed!" << std::endl;
|
||||
if (!continually)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
app().getLoop()->runAfter(5.0, [continually]() {
|
||||
if (!continually)
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
});
|
||||
app().setLogLevel(trantor::Logger::kTrace);
|
||||
app().run();
|
||||
LOG_DEBUG << "bye!";
|
||||
return 0;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<pre id="log"></pre>
|
||||
<script>
|
||||
// helper function: log message to screen
|
||||
function log(msg) {
|
||||
document.getElementById('log').textContent += msg + '\n';
|
||||
}
|
||||
|
||||
// setup websocket with callbacks
|
||||
var ws = new WebSocket("ws://127.0.0.1:8848/chat");
|
||||
ws.onopen = function() {
|
||||
log('CONNECT');
|
||||
ws.send("hello!!!");
|
||||
};
|
||||
ws.onclose = function() {
|
||||
log('DISCONNECT');
|
||||
};
|
||||
ws.onmessage = function(event) {
|
||||
log('MESSAGE: ' + event.data);
|
||||
// ws.send(event.data);
|
||||
// ws.send(event.data);
|
||||
};
|
||||
</script>
|
80
examples/websocket_client/WebSocketClient.cc
Normal file
80
examples/websocket_client/WebSocketClient.cc
Normal file
@ -0,0 +1,80 @@
|
||||
#include <drogon/WebSocketClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
std::string server;
|
||||
std::string path;
|
||||
optional<uint16_t> port;
|
||||
// Connect to a public echo server
|
||||
if (argc > 1 && std::string(argv[1]) == "-p")
|
||||
{
|
||||
server = "wss://echo.websocket.org";
|
||||
path = "/";
|
||||
}
|
||||
else
|
||||
{
|
||||
server = "ws://127.0.0.1";
|
||||
port = 8848;
|
||||
path = "/chat";
|
||||
}
|
||||
|
||||
WebSocketClientPtr wsPtr;
|
||||
if (port.value_or(0) != 0)
|
||||
wsPtr = WebSocketClient::newWebSocketClient(server, port.value());
|
||||
else
|
||||
wsPtr = WebSocketClient::newWebSocketClient(server);
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath(path);
|
||||
|
||||
wsPtr->setMessageHandler([](const std::string &message,
|
||||
const WebSocketClientPtr &wsPtr,
|
||||
const WebSocketMessageType &type) {
|
||||
std::string messageType = "Unknown";
|
||||
if (type == WebSocketMessageType::Text)
|
||||
messageType = "text";
|
||||
else if (type == WebSocketMessageType::Pong)
|
||||
messageType = "pong";
|
||||
else if (type == WebSocketMessageType::Ping)
|
||||
messageType = "ping";
|
||||
else if (type == WebSocketMessageType::Binary)
|
||||
messageType = "binary";
|
||||
else if (type == WebSocketMessageType::Close)
|
||||
messageType = "Close";
|
||||
|
||||
LOG_INFO << "new message (" << messageType << "): " << message;
|
||||
});
|
||||
|
||||
wsPtr->setConnectionClosedHandler([](const WebSocketClientPtr &wsPtr) {
|
||||
LOG_INFO << "WebSocket connection closed!";
|
||||
});
|
||||
|
||||
LOG_INFO << "Connecting to WebSocket at " << server;
|
||||
wsPtr->connectToServer(
|
||||
req,
|
||||
[](ReqResult r,
|
||||
const HttpResponsePtr &resp,
|
||||
const WebSocketClientPtr &wsPtr) {
|
||||
if (r != ReqResult::Ok)
|
||||
{
|
||||
LOG_ERROR << "Failed to establish WebSocket connection!";
|
||||
exit(1);
|
||||
}
|
||||
LOG_INFO << "WebSocket connected!";
|
||||
wsPtr->getConnection()->setPingMessage("", 2s);
|
||||
wsPtr->getConnection()->send("hello!");
|
||||
});
|
||||
|
||||
// Quit the application after 15 seconds
|
||||
app().getLoop()->runAfter(15, []() { app().quit(); });
|
||||
|
||||
app().setLogLevel(trantor::Logger::kDebug);
|
||||
app().run();
|
||||
LOG_INFO << "bye!";
|
||||
return 0;
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
#include <unordered_map>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <type_traits>
|
||||
|
||||
namespace drogon
|
||||
{
|
||||
@ -141,31 +142,11 @@ class DROGON_EXPORT HttpViewData
|
||||
@endcode
|
||||
*/
|
||||
static std::string htmlTranslate(const char *str, size_t length);
|
||||
static std::string htmlTranslate(const std::string &str)
|
||||
{
|
||||
return htmlTranslate(str.data(), str.length());
|
||||
}
|
||||
static std::string htmlTranslate(const string_view &str)
|
||||
{
|
||||
return htmlTranslate(str.data(), str.length());
|
||||
}
|
||||
static bool needTranslation(const std::string &str)
|
||||
{
|
||||
for (auto const &c : str)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '&':
|
||||
case '<':
|
||||
case '>':
|
||||
return true;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool needTranslation(const string_view &str)
|
||||
{
|
||||
for (auto const &c : str)
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace drogon
|
||||
|
984
lib/inc/drogon/drogon_test.h
Normal file
984
lib/inc/drogon/drogon_test.h
Normal file
@ -0,0 +1,984 @@
|
||||
#pragma once
|
||||
#include <trantor/utils/NonCopyable.h>
|
||||
#include <drogon/DrObject.h>
|
||||
#include <drogon/utils/string_view.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <iomanip>
|
||||
#include <cstddef>
|
||||
/**
|
||||
* @brief Drogon Test is a minimal effort test framework developed because the
|
||||
* major C++ test frameworks doesn't handle async programs well. Drogon Test's
|
||||
* syntax is inspired by both Google Test and Catch2
|
||||
*/
|
||||
namespace drogon
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
#define TEST_CTX drogon_test_ctx_
|
||||
#define DROGON_TESTCASE_PREIX_ drtest__
|
||||
#define DROGON_TESTCASE_PREIX_STR_ "drtest__"
|
||||
#define TEST_FLAG_ drgood__
|
||||
|
||||
#define DROGON_TEST_STRINGIFY__(x) #x
|
||||
#define DROGON_TEST_STRINGIFY(x) DROGON_TEST_STRINGIFY__(x)
|
||||
#define DROGON_TEST_CONCAT__(a, b) a##b
|
||||
#define DROGON_TEST_CONCAT(a, b) DROGON_TEST_CONCAT__(a, b)
|
||||
class Case;
|
||||
|
||||
namespace internal
|
||||
{
|
||||
extern std::mutex mtxRegister;
|
||||
extern std::promise<void> allTestRan;
|
||||
extern std::mutex mtxTestStats;
|
||||
extern bool testHasPrinted;
|
||||
extern std::set<Case*> registeredTests;
|
||||
extern std::atomic<size_t> numAssertions;
|
||||
extern std::atomic<size_t> numCorrectAssertions;
|
||||
extern size_t numTestCases;
|
||||
extern std::atomic<size_t> numFailedTestCases;
|
||||
extern bool printSuccessfulTests;
|
||||
inline void registerCase(Case* test)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(mtxRegister);
|
||||
registeredTests.insert(test);
|
||||
}
|
||||
|
||||
inline void unregisterCase(Case* test)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(mtxRegister);
|
||||
registeredTests.erase(test);
|
||||
|
||||
if (registeredTests.empty())
|
||||
allTestRan.set_value();
|
||||
}
|
||||
|
||||
template <typename _Tp, typename dummy = void>
|
||||
struct is_printable : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename _Tp>
|
||||
struct is_printable<_Tp,
|
||||
typename std::enable_if<
|
||||
std::is_same<decltype(std::cout << std::declval<_Tp>()),
|
||||
std::ostream&>::value>::type>
|
||||
: std::true_type
|
||||
{
|
||||
};
|
||||
|
||||
inline std::string escapeString(const string_view sv)
|
||||
{
|
||||
std::string result;
|
||||
result.reserve(sv.size());
|
||||
for (auto ch : sv)
|
||||
{
|
||||
if (ch == '\n')
|
||||
result += "\\n";
|
||||
else if (ch == '\r')
|
||||
result += "\\r";
|
||||
else if (ch == '\t')
|
||||
result += "\\t";
|
||||
else if (ch == '\b')
|
||||
result += "\\b";
|
||||
else if (ch == '\\')
|
||||
result += "\\\\";
|
||||
else if (ch == '"')
|
||||
result += "\"";
|
||||
else if (ch == '\v')
|
||||
result += "\\v";
|
||||
else if (ch == '\a')
|
||||
result += "\\a";
|
||||
else
|
||||
result.push_back(ch);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string prettifyString(const string_view sv, size_t maxLength = 120)
|
||||
{
|
||||
if (sv.size() <= maxLength)
|
||||
return "\"" + escapeString(sv) + "\"";
|
||||
|
||||
const std::string msg = "...\" (truncated)";
|
||||
return "\"" + escapeString(sv.substr(0, maxLength)) + msg;
|
||||
}
|
||||
|
||||
namespace internal
|
||||
{
|
||||
template <bool P>
|
||||
struct AttemptPrintViaStream
|
||||
{
|
||||
template <typename T>
|
||||
std::string operator()(const T& v)
|
||||
{
|
||||
return "{un-printable}";
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AttemptPrintViaStream<true>
|
||||
{
|
||||
template <typename T>
|
||||
std::string operator()(const T& v)
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << v;
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
struct StringPrinter
|
||||
{
|
||||
std::string operator()(const string_view& v)
|
||||
{
|
||||
return prettifyString(v);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename T>
|
||||
inline std::string attemptPrint(T&& v)
|
||||
{
|
||||
using DefaultPrinter =
|
||||
internal::AttemptPrintViaStream<is_printable<T>::value>;
|
||||
|
||||
// Poor man's if constexpr because SFINAE don't disambiguate between
|
||||
// possible resolutions
|
||||
return typename std::conditional<std::is_convertible<T, string_view>::value,
|
||||
internal::StringPrinter,
|
||||
DefaultPrinter>::type()(v);
|
||||
}
|
||||
|
||||
// Specializations to reduce template construction
|
||||
template <>
|
||||
inline std::string attemptPrint(const std::nullptr_t& v)
|
||||
{
|
||||
return "nullptr";
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::string attemptPrint(const char& v)
|
||||
{
|
||||
return "'" + std::string(1, v) + "'";
|
||||
}
|
||||
|
||||
inline std::string stringifyFuncCall(const std::string& funcName)
|
||||
{
|
||||
return funcName + "()";
|
||||
}
|
||||
|
||||
inline std::string stringifyFuncCall(const std::string& funcName,
|
||||
const std::string& param1)
|
||||
{
|
||||
return funcName + "(" + param1 + ")";
|
||||
}
|
||||
|
||||
inline std::string stringifyFuncCall(const std::string& funcName,
|
||||
const std::string& param1,
|
||||
const std::string& param2)
|
||||
{
|
||||
return funcName + "(" + param1 + ", " + param2 + ")";
|
||||
}
|
||||
|
||||
struct ComparsionResult
|
||||
{
|
||||
std::pair<bool, std::string> result() const
|
||||
{
|
||||
return {comparsionResilt_, expansion_};
|
||||
}
|
||||
|
||||
bool comparsionResilt_;
|
||||
std::string expansion_;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Lhs
|
||||
{
|
||||
template <typename _ = void> // HACK: prevent this function to be evaulated
|
||||
// when not invoked
|
||||
std::pair<bool, std::string> result() const
|
||||
{
|
||||
return {(bool)ref_, attemptPrint(ref_)};
|
||||
}
|
||||
|
||||
Lhs(const T& lhs) : ref_(lhs)
|
||||
{
|
||||
}
|
||||
const T& ref_;
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator<(const RhsType& rhs)
|
||||
{
|
||||
return ComparsionResult{ref_ < rhs,
|
||||
attemptPrint(ref_) + " < " +
|
||||
attemptPrint(ref_)};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator>(const RhsType& rhs)
|
||||
{
|
||||
return ComparsionResult{ref_ > rhs,
|
||||
attemptPrint(ref_) + " > " + attemptPrint(rhs)};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator<=(const RhsType& rhs)
|
||||
{
|
||||
return ComparsionResult{ref_ <= rhs,
|
||||
attemptPrint(ref_) +
|
||||
" <= " + attemptPrint(rhs)};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator>=(const RhsType& rhs)
|
||||
{
|
||||
return ComparsionResult{ref_ >= rhs,
|
||||
attemptPrint(ref_) +
|
||||
" >= " + attemptPrint(rhs)};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator==(const RhsType& rhs)
|
||||
{
|
||||
return ComparsionResult{ref_ == rhs,
|
||||
attemptPrint(ref_) +
|
||||
" == " + attemptPrint(rhs)};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator!=(const RhsType& rhs)
|
||||
{
|
||||
return ComparsionResult{ref_ != rhs,
|
||||
attemptPrint(ref_) +
|
||||
" != " + attemptPrint(rhs)};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator&&(const RhsType& rhs)
|
||||
{
|
||||
static_assert(!std::is_same<RhsType, void>::value,
|
||||
" && is not supported in expression decomposition");
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator||(const RhsType& rhs)
|
||||
{
|
||||
static_assert(!std::is_same<RhsType, void>::value,
|
||||
" || is not supported in expression decomposition");
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator|(const RhsType& rhs)
|
||||
{
|
||||
static_assert(!std::is_same<RhsType, void>::value,
|
||||
" | is not supported in expression decomposition");
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename RhsType>
|
||||
ComparsionResult operator&(const RhsType& rhs)
|
||||
{
|
||||
static_assert(!std::is_same<RhsType, void>::value,
|
||||
" & is not supported in expression decomposition");
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct Decomposer
|
||||
{
|
||||
template <typename T>
|
||||
Lhs<T> operator<=(const T& other)
|
||||
{
|
||||
return Lhs<T>(other);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class ThreadSafeStream final
|
||||
{
|
||||
public:
|
||||
ThreadSafeStream(std::ostream& os) : os_(os)
|
||||
{
|
||||
mtx_.lock();
|
||||
}
|
||||
|
||||
~ThreadSafeStream()
|
||||
{
|
||||
mtx_.unlock();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::ostream& operator<<(const T& rhs)
|
||||
{
|
||||
return os_ << rhs;
|
||||
}
|
||||
|
||||
static std::mutex mtx_;
|
||||
std::ostream& os_;
|
||||
};
|
||||
|
||||
inline ThreadSafeStream print()
|
||||
{
|
||||
return ThreadSafeStream(std::cout);
|
||||
}
|
||||
|
||||
inline ThreadSafeStream printErr()
|
||||
{
|
||||
return ThreadSafeStream(std::cerr);
|
||||
}
|
||||
|
||||
class CaseBase : public trantor::NonCopyable
|
||||
{
|
||||
public:
|
||||
CaseBase() = default;
|
||||
CaseBase(const std::string& name) : name_(name)
|
||||
{
|
||||
}
|
||||
CaseBase(std::shared_ptr<CaseBase> parent, const std::string& name)
|
||||
: parent_(parent), name_(name)
|
||||
{
|
||||
}
|
||||
virtual ~CaseBase() = default;
|
||||
|
||||
std::string fullname() const
|
||||
{
|
||||
std::string result;
|
||||
auto curr = this;
|
||||
while (curr != nullptr)
|
||||
{
|
||||
result = curr->name() + result;
|
||||
if (curr->parent_ != nullptr)
|
||||
result = "." + result;
|
||||
curr = curr->parent_.get();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::string& name() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
|
||||
void setFailed()
|
||||
{
|
||||
if (failed_ == false)
|
||||
{
|
||||
internal::numFailedTestCases++;
|
||||
failed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool failed() const
|
||||
{
|
||||
return failed_;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool failed_ = false;
|
||||
std::string name_;
|
||||
std::shared_ptr<CaseBase> parent_ = nullptr;
|
||||
};
|
||||
|
||||
class Case : public CaseBase
|
||||
{
|
||||
public:
|
||||
Case(const std::string& name) : CaseBase(name)
|
||||
{
|
||||
internal::registerCase(this);
|
||||
}
|
||||
|
||||
Case(std::shared_ptr<Case> parent, const std::string& name)
|
||||
: CaseBase(parent, name)
|
||||
{
|
||||
internal::registerCase(this);
|
||||
}
|
||||
|
||||
virtual ~Case()
|
||||
{
|
||||
internal::unregisterCase(this);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestCase : public CaseBase
|
||||
{
|
||||
TestCase(const std::string& name) : CaseBase(name)
|
||||
{
|
||||
}
|
||||
virtual ~TestCase() = default;
|
||||
virtual void doTest_(std::shared_ptr<Case>) = 0;
|
||||
};
|
||||
|
||||
void printTestStats();
|
||||
|
||||
#ifdef DROGON_TEST_MAIN
|
||||
|
||||
namespace internal
|
||||
{
|
||||
static std::string leftpad(const std::string& str, size_t len)
|
||||
{
|
||||
if (len <= str.size())
|
||||
return str;
|
||||
return std::string(len - str.size(), ' ') + str;
|
||||
}
|
||||
} // namespace internal
|
||||
|
||||
static void printHelp(string_view argv0)
|
||||
{
|
||||
print() << "A Drogon Test application:\n\n"
|
||||
<< "Usage: " << argv0 << " [options]\n"
|
||||
<< "options:\n"
|
||||
<< " -r Run a specific test\n"
|
||||
<< " -s Print successful tests\n"
|
||||
<< " -l List avaliable tests\n"
|
||||
<< " -h Print this help message\n";
|
||||
}
|
||||
|
||||
void printTestStats()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(internal::mtxTestStats);
|
||||
if (internal::testHasPrinted)
|
||||
return;
|
||||
const size_t successAssertions = internal::numCorrectAssertions;
|
||||
const size_t totalAssertions = internal::numAssertions;
|
||||
const size_t successTests =
|
||||
internal::numTestCases - internal::numFailedTestCases;
|
||||
const size_t totalTests = internal::numTestCases;
|
||||
|
||||
float ratio;
|
||||
if (totalAssertions != 0)
|
||||
ratio = (float)successTests / totalTests;
|
||||
else
|
||||
ratio = 1;
|
||||
const size_t barSize = 80;
|
||||
size_t greenBar = barSize * ratio;
|
||||
size_t redBar = barSize * (1 - ratio);
|
||||
if (greenBar + redBar != barSize)
|
||||
{
|
||||
float fraction = (ratio * barSize) - (size_t)(ratio * barSize);
|
||||
if (fraction >= 0.5f)
|
||||
greenBar++;
|
||||
else
|
||||
redBar++;
|
||||
}
|
||||
if (successAssertions != totalAssertions && redBar == 0)
|
||||
{
|
||||
redBar = 1;
|
||||
greenBar--;
|
||||
}
|
||||
|
||||
print() << "\n\x1B[0;31m" << std::string(redBar, '=') << "\x1B[0;32m"
|
||||
<< std::string(greenBar, '=') << "\x1B[0m\n";
|
||||
|
||||
if (successAssertions == totalAssertions)
|
||||
{
|
||||
print() << "\x1B[1;32m All tests passed\x1B[0m (" << totalAssertions
|
||||
<< " assertions in " << totalTests << " tests cases).\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string totalAssertsionStr = std::to_string(totalAssertions);
|
||||
std::string successAssertionsStr = std::to_string(successAssertions);
|
||||
std::string failedAssertsionStr =
|
||||
std::to_string(totalAssertions - successAssertions);
|
||||
std::string totalTestsStr = std::to_string(totalTests);
|
||||
std::string successTestsStr = std::to_string(successTests);
|
||||
std::string failedTestsStr = std::to_string(totalTests - successTests);
|
||||
const size_t totalLen =
|
||||
(std::max)(totalAssertsionStr.size(), totalTestsStr.size());
|
||||
const size_t successLen =
|
||||
(std::max)(successAssertionsStr.size(), successTestsStr.size());
|
||||
const size_t failedLen =
|
||||
(std::max)(failedAssertsionStr.size(), failedTestsStr.size());
|
||||
using internal::leftpad;
|
||||
print() << "assertions: " << leftpad(totalAssertsionStr, totalLen)
|
||||
<< " | \x1B[0;32m" << leftpad(successAssertionsStr, successLen)
|
||||
<< " passed\x1B[0m | \x1B[0;31m"
|
||||
<< leftpad(failedAssertsionStr, failedLen) << " failed\x1B[0m\n"
|
||||
<< "test cases: " << leftpad(totalTestsStr, totalLen)
|
||||
<< " | \x1B[0;32m" << leftpad(successTestsStr, successLen)
|
||||
<< " passed\x1B[0m | \x1B[0;31m"
|
||||
<< leftpad(failedTestsStr, failedLen) << " failed\x1B[0m\n";
|
||||
}
|
||||
internal::testHasPrinted = true;
|
||||
}
|
||||
|
||||
static int run(int argc, char** argv)
|
||||
{
|
||||
internal::numCorrectAssertions = 0;
|
||||
internal::numAssertions = 0;
|
||||
internal::numFailedTestCases = 0;
|
||||
internal::numTestCases = 0;
|
||||
internal::printSuccessfulTests = false;
|
||||
|
||||
std::string targetTest;
|
||||
bool listTests = false;
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
std::string param = argv[i];
|
||||
if (param == "-r" && i + 1 < argc)
|
||||
{
|
||||
targetTest = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
if (param == "-h")
|
||||
{
|
||||
printHelp(argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
if (param == "-s")
|
||||
{
|
||||
internal::printSuccessfulTests = true;
|
||||
}
|
||||
if (param == "-l")
|
||||
{
|
||||
listTests = true;
|
||||
}
|
||||
}
|
||||
auto classNames = DrClassMap::getAllClassName();
|
||||
|
||||
if (listTests)
|
||||
{
|
||||
print() << "Avaliable Tests:\n";
|
||||
for (const auto& name : classNames)
|
||||
{
|
||||
if (name.find(DROGON_TESTCASE_PREIX_STR_) == 0)
|
||||
{
|
||||
auto test =
|
||||
std::unique_ptr<DrObjectBase>(DrClassMap::newObject(name));
|
||||
auto ptr = dynamic_cast<TestCase*>(test.get());
|
||||
if (ptr == nullptr)
|
||||
continue;
|
||||
print() << " " << ptr->name() << "\n";
|
||||
}
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<TestCase>> testCases;
|
||||
// NOTE: Registering a dummy case prevents the test-end signal to be
|
||||
// emited too early as there's always an case that hasn't finish
|
||||
std::shared_ptr<Case> dummyCase = std::make_shared<Case>("__dummy_dummy_");
|
||||
for (const auto& name : classNames)
|
||||
{
|
||||
if (name.find(DROGON_TESTCASE_PREIX_STR_) == 0)
|
||||
{
|
||||
auto obj =
|
||||
std::shared_ptr<DrObjectBase>(DrClassMap::newObject(name));
|
||||
auto test = std::dynamic_pointer_cast<TestCase>(obj);
|
||||
if (test == nullptr)
|
||||
{
|
||||
LOG_WARN << "Class " << name
|
||||
<< " seems to be a test case. But type information "
|
||||
"disagrees.";
|
||||
continue;
|
||||
}
|
||||
if (targetTest.empty() || test->name() == targetTest)
|
||||
{
|
||||
internal::numTestCases++;
|
||||
test->doTest_(std::make_shared<Case>(test->name()));
|
||||
testCases.emplace_back(std::move(test));
|
||||
}
|
||||
}
|
||||
}
|
||||
dummyCase = {};
|
||||
|
||||
if (targetTest != "" && internal::numTestCases == 0)
|
||||
{
|
||||
printErr() << "Cannot find test named " << targetTest << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (internal::registeredTests.empty() == false)
|
||||
{
|
||||
auto fut = internal::allTestRan.get_future();
|
||||
fut.get();
|
||||
assert(internal::registeredTests.empty());
|
||||
}
|
||||
testCases.clear();
|
||||
|
||||
printTestStats();
|
||||
|
||||
return internal::numCorrectAssertions != internal::numAssertions;
|
||||
}
|
||||
#endif
|
||||
} // namespace test
|
||||
} // namespace drogon
|
||||
|
||||
#define ERROR_MSG(func_name, expr) \
|
||||
drogon::test::printErr() \
|
||||
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
|
||||
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
|
||||
<< " \x1B[0;31m FAILED:\x1B[0m\n" \
|
||||
<< " \033[0;34m" \
|
||||
<< drogon::test::internal::stringifyFuncCall(func_name, expr) \
|
||||
<< "\x1B[0m\n"
|
||||
|
||||
#define PASSED_MSG(func_name, expr) \
|
||||
drogon::test::print() \
|
||||
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
|
||||
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
|
||||
<< " \x1B[0;32m PASSED:\x1B[0m\n" \
|
||||
<< " \033[0;34m" \
|
||||
<< drogon::test::internal::stringifyFuncCall(func_name, expr) \
|
||||
<< "\x1B[0m\n"
|
||||
|
||||
#define SET_TEST_SUCCESS__ \
|
||||
do \
|
||||
{ \
|
||||
TEST_FLAG_ = true; \
|
||||
} while (0);
|
||||
|
||||
#define TEST_INTERNAL__(func_name, \
|
||||
expr, \
|
||||
eval, \
|
||||
on_exception, \
|
||||
on_non_standard_exception, \
|
||||
on_leaving) \
|
||||
do \
|
||||
{ \
|
||||
bool TEST_FLAG_ = false; \
|
||||
using drogon::test::internal::stringifyFuncCall; \
|
||||
using drogon::test::printErr; \
|
||||
drogon::test::internal::numAssertions++; \
|
||||
try \
|
||||
{ \
|
||||
eval; \
|
||||
} \
|
||||
catch (const std::exception& e) \
|
||||
{ \
|
||||
on_exception; \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
on_non_standard_exception; \
|
||||
} \
|
||||
if (TEST_FLAG_) \
|
||||
drogon::test::internal::numCorrectAssertions++; \
|
||||
else \
|
||||
TEST_CTX->setFailed(); \
|
||||
on_leaving; \
|
||||
} while (0);
|
||||
|
||||
#define EVAL_AND_CHECK_TRUE__(func_name, expr) \
|
||||
do \
|
||||
{ \
|
||||
bool drresult__; \
|
||||
std::string drexpansion__; \
|
||||
std::tie(drresult__, drexpansion__) = \
|
||||
(drogon::test::internal::Decomposer() <= expr).result(); \
|
||||
if (!drresult__) \
|
||||
{ \
|
||||
ERROR_MSG(func_name, #expr) \
|
||||
<< "With expansion\n" \
|
||||
<< " \033[0;33m" << drexpansion__ << "\x1B[0m\n\n"; \
|
||||
} \
|
||||
else \
|
||||
SET_TEST_SUCCESS__; \
|
||||
} while (0);
|
||||
|
||||
#define PRINT_UNEXPECTED_EXCEPTION__(func_name, expr) \
|
||||
do \
|
||||
{ \
|
||||
ERROR_MSG(func_name, expr) \
|
||||
<< "An unexpected exception is thrown. what():\n" \
|
||||
<< " \033[0;33m" << e.what() << "\x1B[0m\n\n"; \
|
||||
} while (0);
|
||||
|
||||
#define PRINT_PASSED__(func_name, expr) \
|
||||
do \
|
||||
{ \
|
||||
if (drogon::test::internal::printSuccessfulTests == true) \
|
||||
{ \
|
||||
PASSED_MSG(func_name, expr) << "\n"; \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define RETURN_ON_FAILURE__ \
|
||||
do \
|
||||
{ \
|
||||
if (!TEST_FLAG_) \
|
||||
return; \
|
||||
} while (0);
|
||||
|
||||
#define CO_RETURN_ON_FAILURE__ \
|
||||
do \
|
||||
{ \
|
||||
if (!TEST_FLAG_) \
|
||||
co_return; \
|
||||
} while (0);
|
||||
|
||||
#define DIE_ON_FAILURE__ \
|
||||
do \
|
||||
{ \
|
||||
using namespace drogon::test; \
|
||||
if (!TEST_FLAG_) \
|
||||
{ \
|
||||
printTestStats(); \
|
||||
printErr() << "Force exiting due to a mandation failed.\n"; \
|
||||
exit(1); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define PRINT_NONSTANDARD_EXCEPTION__(func_name, expr) \
|
||||
do \
|
||||
{ \
|
||||
ERROR_MSG(func_name, expr) \
|
||||
<< "Unexpected unknown exception is thrown.\n\n"; \
|
||||
} while (0);
|
||||
|
||||
#define EVAL__(expr) \
|
||||
do \
|
||||
{ \
|
||||
expr; \
|
||||
} while (0);
|
||||
|
||||
#define NOTHING__ \
|
||||
{ \
|
||||
}
|
||||
|
||||
#define PRINT_ERR_NOEXCEPTION__(expr, func_name) \
|
||||
do \
|
||||
{ \
|
||||
if (!TEST_FLAG_) \
|
||||
ERROR_MSG(func_name, expr) \
|
||||
<< "With expecitation\n" \
|
||||
<< " Expected to throw an exception. But non are " \
|
||||
"thrown.\n\n"; \
|
||||
} while (0);
|
||||
|
||||
#define PRINT_ERR_WITHEXCEPTION__(expr, func_name) \
|
||||
do \
|
||||
{ \
|
||||
if (!TEST_FLAG_) \
|
||||
ERROR_MSG(func_name, expr) \
|
||||
<< "With expecitation\n" \
|
||||
<< " Should to not throw an exception. But one is " \
|
||||
"thrown.\n\n"; \
|
||||
} while (0);
|
||||
|
||||
#define PRINT_ERR_BAD_EXCEPTION__( \
|
||||
expr, func_name, excep_type, exceptionThrown, correctExceptionType) \
|
||||
do \
|
||||
{ \
|
||||
assert((exceptionThrown && correctExceptionType) || !exceptionThrown); \
|
||||
if (exceptionThrown == true && correctExceptionType == false) \
|
||||
{ \
|
||||
ERROR_MSG(func_name, expr) \
|
||||
<< "With expecitation\n" \
|
||||
<< " Exception have been throw but not of type \033[0;33m" \
|
||||
<< #excep_type << "\033[0m.\n\n"; \
|
||||
} \
|
||||
else if (exceptionThrown == false) \
|
||||
{ \
|
||||
ERROR_MSG(func_name, expr) \
|
||||
<< "With expecitation\n" \
|
||||
<< " A \033[0;33m" << #excep_type \
|
||||
<< "\033[0m exception is expected. But nothing was thrown" \
|
||||
<< "\033[0m.\n\n"; \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define CHECK_INTERNAL__(expr, func_name, on_leave) \
|
||||
do \
|
||||
{ \
|
||||
TEST_INTERNAL__(func_name, \
|
||||
expr, \
|
||||
EVAL_AND_CHECK_TRUE__(func_name, expr), \
|
||||
PRINT_UNEXPECTED_EXCEPTION__(func_name, #expr), \
|
||||
PRINT_NONSTANDARD_EXCEPTION__(func_name, #expr), \
|
||||
on_leave PRINT_PASSED__(func_name, #expr)); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_THROWS_INTERNAL__(expr, func_name, on_leave) \
|
||||
do \
|
||||
{ \
|
||||
TEST_INTERNAL__(func_name, \
|
||||
expr, \
|
||||
EVAL__(expr), \
|
||||
SET_TEST_SUCCESS__, \
|
||||
SET_TEST_SUCCESS__, \
|
||||
PRINT_ERR_NOEXCEPTION__(#expr, func_name) \
|
||||
on_leave PRINT_PASSED__(func_name, #expr)); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_THROWS_AS_INTERNAL__(expr, func_name, except_type, on_leave) \
|
||||
do \
|
||||
{ \
|
||||
bool exceptionThrown = false; \
|
||||
TEST_INTERNAL__( \
|
||||
func_name, \
|
||||
expr, \
|
||||
EVAL__(expr), \
|
||||
{ \
|
||||
exceptionThrown = true; \
|
||||
if (dynamic_cast<const except_type*>(&e) != nullptr) \
|
||||
SET_TEST_SUCCESS__; \
|
||||
}, \
|
||||
{ exceptionThrown = true; }, \
|
||||
PRINT_ERR_BAD_EXCEPTION__(#expr ", " #except_type, \
|
||||
func_name, \
|
||||
except_type, \
|
||||
exceptionThrown, \
|
||||
TEST_FLAG_) \
|
||||
on_leave PRINT_PASSED__(func_name, #expr ", " #except_type)); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK_NOTHROW_INTERNAL__(expr, func_name, on_leave) \
|
||||
do \
|
||||
{ \
|
||||
TEST_INTERNAL__(func_name, \
|
||||
expr, \
|
||||
EVAL__(expr) SET_TEST_SUCCESS__, \
|
||||
NOTHING__, \
|
||||
NOTHING__, \
|
||||
PRINT_ERR_WITHEXCEPTION__(#expr, func_name) \
|
||||
on_leave PRINT_PASSED__(func_name, #expr)); \
|
||||
} while (0)
|
||||
|
||||
#define CHECK(expr) CHECK_INTERNAL__(expr, "CHECK", NOTHING__)
|
||||
#define CHECK_THROWS(expr) \
|
||||
CHECK_THROWS_INTERNAL__(expr, "CHECK_THROWS", NOTHING__)
|
||||
#define CHECK_NOTHROW(expr) \
|
||||
CHECK_NOTHROW_INTERNAL__(expr, "CHECK_NOTHROW", NOTHING__)
|
||||
#define CHECK_THROWS_AS(expr, except_type) \
|
||||
CHECK_THROWS_AS_INTERNAL__(expr, "CHECK_THROWS_AS", except_type, NOTHING__)
|
||||
|
||||
#define REQUIRE(expr) CHECK_INTERNAL__(expr, "REQUIRE", RETURN_ON_FAILURE__)
|
||||
#define REQUIRE_THROWS(expr) \
|
||||
CHECK_THROWS_INTERNAL__(expr, "REQUIRE_THROWS", RETURN_ON_FAILURE__)
|
||||
#define REQUIRE_NOTHROW(expr) \
|
||||
CHECK_NOTHROW_INTERNAL__(expr, "REQUIRE_NOTHROW", RETURN_ON_FAILURE__)
|
||||
#define REQUIRE_THROWS_AS(expr, except_type) \
|
||||
CHECK_THROWS_AS_INTERNAL__(expr, \
|
||||
"REQUIRE_THROWS_AS", \
|
||||
except_type, \
|
||||
RETURN_ON_FAILURE__)
|
||||
|
||||
#define CO_REQUIRE(expr) \
|
||||
CHECK_INTERNAL__(expr, "CO_REQUIRE", CO_RETURN_ON_FAILURE__)
|
||||
#define CO_REQUIRE_THROWS(expr) \
|
||||
CHECK_THROWS_INTERNAL__(expr, "CO_REQUIRE_THROWS", CO_RETURN_ON_FAILURE__)
|
||||
#define CO_REQUIRE_NOTHROW(expr) \
|
||||
CHECK_NOTHROW_INTERNAL__(expr, "CO_REQUIRE_NOTHROW", CO_RETURN_ON_FAILURE__)
|
||||
#define CO_REQUIRE_THROWS_AS(expr, except_type) \
|
||||
CHECK_THROWS_AS_INTERNAL__(expr, \
|
||||
"CO_REQUIRE_THROWS_AS", \
|
||||
except_type, \
|
||||
CO_RETURN_ON_FAILURE__)
|
||||
|
||||
#define MANDATE(expr) CHECK_INTERNAL__(expr, "MANDATE", DIE_ON_FAILURE__)
|
||||
#define MANDATE_THROWS(expr) \
|
||||
CHECK_THROWS_INTERNAL__(expr, "MANDATE_THROWS", DIE_ON_FAILURE__)
|
||||
#define MANDATE_NOTHROW(expr) \
|
||||
CHECK_NOTHROW_INTERNAL__(expr, "MANDATE_NOTHROW", DIE_ON_FAILURE__)
|
||||
#define MANDATE_THROWS_AS(expr, except_type) \
|
||||
CHECK_THROWS_AS_INTERNAL__(expr, \
|
||||
"MANDATE_THROWS_AS", \
|
||||
except_type, \
|
||||
DIE_ON_FAILURE__)
|
||||
|
||||
#define STATIC_REQUIRE(expr) \
|
||||
do \
|
||||
{ \
|
||||
TEST_CTX; \
|
||||
drogon::test::internal::numAssertions++; \
|
||||
static_assert((expr), #expr " failed."); \
|
||||
drogon::test::internal::numCorrectAssertions++; \
|
||||
} while (0);
|
||||
|
||||
#define FAIL(message) \
|
||||
do \
|
||||
{ \
|
||||
using namespace drogon::test; \
|
||||
TEST_CTX->setFailed(); \
|
||||
printErr() << "\x1B[1;37mIn test case " << TEST_CTX->fullname() \
|
||||
<< "\n" \
|
||||
<< "\x1B[0;37m" << __FILE__ << ":" << __LINE__ \
|
||||
<< " \x1B[0;31m FAILED:\x1B[0m\n" \
|
||||
<< " Reason: " << message << "\n\n"; \
|
||||
drogon::test::internal::numAssertions++; \
|
||||
} while (0)
|
||||
#define FAULT(message) \
|
||||
do \
|
||||
{ \
|
||||
using namespace drogon::test; \
|
||||
FAIL(message); \
|
||||
printTestStats(); \
|
||||
printErr() << "Force exiting due to a FAULT statement.\n"; \
|
||||
exit(1); \
|
||||
} while (0)
|
||||
|
||||
#define SUCCESS() \
|
||||
do \
|
||||
{ \
|
||||
if (drogon::test::internal::printSuccessfulTests) \
|
||||
drogon::test::print() \
|
||||
<< "\x1B[1;37mIn test case " << TEST_CTX->fullname() << "\n" \
|
||||
<< "\x1B[0;37m↳ " << __FILE__ << ":" << __LINE__ \
|
||||
<< " \x1B[0;32m PASSED:\x1B[0m\n" \
|
||||
<< " \033[0;34mSUCCESS()\x1B[0m\n\n"; \
|
||||
TEST_CTX; \
|
||||
drogon::test::internal::numAssertions++; \
|
||||
drogon::test::internal::numCorrectAssertions++; \
|
||||
} while (0)
|
||||
|
||||
#define DROGON_TEST_CLASS_NAME_(test_name) \
|
||||
DROGON_TEST_CONCAT(DROGON_TESTCASE_PREIX_, test_name)
|
||||
|
||||
#define DROGON_TEST(test_name) \
|
||||
struct DROGON_TEST_CLASS_NAME_(test_name) \
|
||||
: public drogon::DrObject<DROGON_TEST_CLASS_NAME_(test_name)>, \
|
||||
public drogon::test::TestCase \
|
||||
{ \
|
||||
DROGON_TEST_CLASS_NAME_(test_name) \
|
||||
() : drogon::test::TestCase(#test_name) \
|
||||
{ \
|
||||
} \
|
||||
inline void doTest_(std::shared_ptr<drogon::test::Case>) override; \
|
||||
}; \
|
||||
void DROGON_TEST_CLASS_NAME_(test_name)::doTest_( \
|
||||
std::shared_ptr<drogon::test::Case> TEST_CTX)
|
||||
#define SUBTEST(name) (std::make_shared<drogon::test::Case>(TEST_CTX, name))
|
||||
#define SUBSECTION(name) \
|
||||
for (std::shared_ptr<drogon::test::Case> ctx_hold__ = TEST_CTX, \
|
||||
ctx_tmp__ = SUBTEST(#name); \
|
||||
ctx_tmp__ != nullptr; \
|
||||
TEST_CTX = ctx_hold__, ctx_tmp__ = nullptr) \
|
||||
if (TEST_CTX = ctx_tmp__, TEST_CTX != nullptr)
|
||||
|
||||
#ifdef DROGON_TEST_MAIN
|
||||
namespace drogon
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
std::mutex ThreadSafeStream::mtx_;
|
||||
|
||||
namespace internal
|
||||
{
|
||||
std::mutex mtxRegister;
|
||||
std::mutex mtxTestStats;
|
||||
bool testHasPrinted = false;
|
||||
std::set<Case*> registeredTests;
|
||||
std::promise<void> allTestRan;
|
||||
std::atomic<size_t> numAssertions;
|
||||
std::atomic<size_t> numCorrectAssertions;
|
||||
size_t numTestCases;
|
||||
std::atomic<size_t> numFailedTestCases;
|
||||
bool printSuccessfulTests;
|
||||
} // namespace internal
|
||||
} // namespace test
|
||||
} // namespace drogon
|
||||
|
||||
#endif
|
@ -145,6 +145,10 @@ struct [[nodiscard]] Task
|
||||
{
|
||||
value = v;
|
||||
}
|
||||
void return_value(T &&v)
|
||||
{
|
||||
value = std::move(v);
|
||||
}
|
||||
|
||||
auto final_suspend() noexcept
|
||||
{
|
||||
@ -211,7 +215,7 @@ struct [[nodiscard]] Task
|
||||
T await_resume()
|
||||
{
|
||||
auto &&v = coro_.promise().result();
|
||||
return v;
|
||||
return std::move(v);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -396,14 +400,10 @@ struct AsyncTask
|
||||
|
||||
AsyncTask(handle_type h) : coro_(h)
|
||||
{
|
||||
if (coro_)
|
||||
coro_.promise().setSelf(coro_);
|
||||
}
|
||||
|
||||
AsyncTask(const AsyncTask &) = delete;
|
||||
|
||||
~AsyncTask()
|
||||
{
|
||||
}
|
||||
AsyncTask &operator=(const AsyncTask &) = delete;
|
||||
AsyncTask &operator=(AsyncTask &&other)
|
||||
{
|
||||
@ -418,7 +418,6 @@ struct AsyncTask
|
||||
struct promise_type
|
||||
{
|
||||
std::coroutine_handle<> continuation_;
|
||||
handle_type self_;
|
||||
|
||||
AsyncTask get_return_object() noexcept
|
||||
{
|
||||
@ -432,8 +431,8 @@ struct AsyncTask
|
||||
|
||||
void unhandled_exception()
|
||||
{
|
||||
LOG_FATAL << "Unhandled exception in AsyncTask.";
|
||||
abort();
|
||||
LOG_FATAL << "Exception escaping AsyncTask.";
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
void return_void() noexcept
|
||||
@ -445,51 +444,27 @@ struct AsyncTask
|
||||
continuation_ = handle;
|
||||
}
|
||||
|
||||
void setSelf(handle_type handle)
|
||||
{
|
||||
self_ = handle;
|
||||
}
|
||||
|
||||
auto final_suspend() const noexcept
|
||||
{
|
||||
// Can't simply use suspend_never because we need symmetric transfer
|
||||
struct awaiter final
|
||||
{
|
||||
awaiter(handle_type h) : self_(h)
|
||||
{
|
||||
}
|
||||
|
||||
awaiter(const awaiter &) = delete;
|
||||
awaiter &operator=(const awaiter &) = delete;
|
||||
|
||||
~awaiter()
|
||||
{
|
||||
if (self_)
|
||||
self_.destroy();
|
||||
}
|
||||
|
||||
bool await_ready() const noexcept
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto await_suspend(
|
||||
std::coroutine_handle<promise_type> coro) const noexcept
|
||||
{
|
||||
return coro.promise().continuation_;
|
||||
}
|
||||
|
||||
void await_resume() const noexcept
|
||||
{
|
||||
}
|
||||
|
||||
std::coroutine_handle<> await_suspend(
|
||||
std::coroutine_handle<promise_type> handle) noexcept
|
||||
{
|
||||
auto coro = handle.promise().continuation_;
|
||||
if (coro)
|
||||
return coro;
|
||||
|
||||
return std::noop_coroutine();
|
||||
}
|
||||
|
||||
handle_type self_;
|
||||
};
|
||||
|
||||
return awaiter(self_);
|
||||
return awaiter{};
|
||||
}
|
||||
};
|
||||
bool await_ready() const noexcept
|
||||
@ -501,9 +476,10 @@ struct AsyncTask
|
||||
{
|
||||
}
|
||||
|
||||
void await_suspend(std::coroutine_handle<> coroutine) noexcept
|
||||
auto await_suspend(std::coroutine_handle<> coroutine) noexcept
|
||||
{
|
||||
coro_.promise().setContinuation(coroutine);
|
||||
return coro_;
|
||||
}
|
||||
|
||||
handle_type coro_;
|
||||
@ -637,21 +613,20 @@ auto sync_wait(Await &&await)
|
||||
|
||||
if (exception_ptr)
|
||||
std::rethrow_exception(exception_ptr);
|
||||
return value.value();
|
||||
|
||||
return std::move(value.value());
|
||||
}
|
||||
}
|
||||
|
||||
// Converts a task (or task like) promise into std::future for old-style async
|
||||
template <typename Await>
|
||||
inline auto co_future(Await await) noexcept
|
||||
inline auto co_future(Await &&await) noexcept
|
||||
-> std::future<await_result_t<Await>>
|
||||
{
|
||||
using Result = await_result_t<Await>;
|
||||
std::promise<Result> prom;
|
||||
auto fut = prom.get_future();
|
||||
[](std::promise<Result> prom,
|
||||
Await await,
|
||||
std::future<AsyncTask *> selfFut) mutable -> AsyncTask {
|
||||
[](std::promise<Result> prom, Await await) -> AsyncTask {
|
||||
try
|
||||
{
|
||||
if constexpr (std::is_void_v<Result>)
|
||||
@ -666,7 +641,7 @@ inline auto co_future(Await await) noexcept
|
||||
{
|
||||
prom.set_exception(std::current_exception());
|
||||
}
|
||||
}();
|
||||
}(std::move(prom), std::move(await));
|
||||
return fut;
|
||||
}
|
||||
|
||||
|
@ -18,22 +18,24 @@ using namespace drogon;
|
||||
std::string Cookie::cookieString() const
|
||||
{
|
||||
std::string ret = "Set-Cookie: ";
|
||||
ret.append(key_).append("= ").append(value_).append("; ");
|
||||
// reserve space to reduce frequency allocation
|
||||
ret.reserve(ret.size() + key_.size() + value_.size() + 30);
|
||||
ret.append(key_).append("=").append(value_).append("; ");
|
||||
if (expiresDate_.microSecondsSinceEpoch() !=
|
||||
(std::numeric_limits<int64_t>::max)() &&
|
||||
expiresDate_.microSecondsSinceEpoch() >= 0)
|
||||
{
|
||||
ret.append("Expires= ")
|
||||
ret.append("Expires=")
|
||||
.append(utils::getHttpFullDate(expiresDate_))
|
||||
.append("; ");
|
||||
}
|
||||
if (!domain_.empty())
|
||||
{
|
||||
ret.append("Domain= ").append(domain_).append("; ");
|
||||
ret.append("Domain=").append(domain_).append("; ");
|
||||
}
|
||||
if (!path_.empty())
|
||||
{
|
||||
ret.append("Path= ").append(path_).append("; ");
|
||||
ret.append("Path=").append(path_).append("; ");
|
||||
}
|
||||
if (secure_)
|
||||
{
|
||||
|
120
lib/tests/CMakeLists.txt
Executable file → Normal file
120
lib/tests/CMakeLists.txt
Executable file → Normal file
@ -1,33 +1,99 @@
|
||||
link_libraries(${PROJECT_NAME})
|
||||
set(UNITTEST_SOURCES unittests/main.cc
|
||||
unittests/Base64Test.cc
|
||||
unittests/UrlCodecTest.cc
|
||||
unittests/GzipTest.cc
|
||||
unittests/HttpViewDataTest.cc
|
||||
unittests/CookieTest.cc
|
||||
unittests/ClassNameTest.cc
|
||||
unittests/HttpDateTest.cc
|
||||
unittests/HttpHeaderTest.cc
|
||||
unittests/MD5Test.cc
|
||||
unittests/MsgBufferTest.cc
|
||||
unittests/OStringStreamTest.cc
|
||||
unittests/PubSubServiceUnittest.cc
|
||||
unittests/Sha1Test.cc
|
||||
../src/ssl_funcs/Sha1.cc
|
||||
unittests/FileTypeTest.cc
|
||||
unittests/DrObjectTest.cc
|
||||
unittests/HttpFullDateTest.cc
|
||||
unittests/MainLoopTest.cc
|
||||
unittests/CacheMapTest.cc
|
||||
unittests/StringOpsTest.cc)
|
||||
|
||||
add_executable(cache_map_test CacheMapTest.cc)
|
||||
add_executable(cache_map_test2 CacheMapTest2.cc)
|
||||
add_executable(cookies_test CookiesTest.cc)
|
||||
add_executable(class_name_test ClassNameTest.cc)
|
||||
add_executable(view_data_test HttpViewDataTest.cc)
|
||||
add_executable(http_full_date_test HttpFullDateTest.cc)
|
||||
add_executable(gzip_test GzipTest.cc)
|
||||
add_executable(url_codec_test UrlCodecTest.cc)
|
||||
add_executable(main_loop_test MainLoopTest.cc)
|
||||
add_executable(main_loop_test2 MainLoopTest2.cc)
|
||||
|
||||
set(test_targets
|
||||
cache_map_test
|
||||
cache_map_test2
|
||||
cookies_test
|
||||
class_name_test
|
||||
view_data_test
|
||||
http_full_date_test
|
||||
gzip_test
|
||||
url_codec_test
|
||||
main_loop_test
|
||||
main_loop_test2)
|
||||
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
add_executable(coroutine_test CoroutineTest.cc)
|
||||
set(test_targets ${test_targets} coroutine_test)
|
||||
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} unittests/CoroutineTest.cc)
|
||||
endif()
|
||||
|
||||
if(Brotli_FOUND)
|
||||
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} unittests/BrotliTest.cc)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND BUILD_DROGON_SHARED)
|
||||
set(UNITTEST_SOURCES ${UNITTEST_SOURCES} ../src/HttpUtils.cc)
|
||||
endif()
|
||||
|
||||
add_executable(unittest ${UNITTEST_SOURCES})
|
||||
|
||||
set(INTEGRATION_TEST_CLIENT_SOURCES integration_test/client/main.cc
|
||||
integration_test/client/WebSocketTest.cc
|
||||
integration_test/client/MultipleWsTest.cc
|
||||
integration_test/client/HttpPipeliningTest.cc)
|
||||
add_executable(integration_test_client ${INTEGRATION_TEST_CLIENT_SOURCES})
|
||||
|
||||
set(INTEGRATION_TEST_SERVER_SOURCES
|
||||
integration_test/server/CustomCtrl.cc
|
||||
integration_test/server/CustomHeaderFilter.cc
|
||||
integration_test/server/DoNothingPlugin.cc
|
||||
integration_test/server/ForwardCtrl.cc
|
||||
integration_test/server/JsonTestController.cc
|
||||
integration_test/server/ListParaCtl.cc
|
||||
integration_test/server/PipeliningTest.cc
|
||||
integration_test/server/TestController.cc
|
||||
integration_test/server/TestPlugin.cc
|
||||
integration_test/server/TestViewCtl.cc
|
||||
integration_test/server/WebSocketTest.cc
|
||||
integration_test/server/api_Attachment.cc
|
||||
integration_test/server/api_v1_ApiTest.cc
|
||||
integration_test/server/TimeFilter.cc
|
||||
integration_test/server/DigestAuthFilter.cc
|
||||
integration_test/server/main.cc)
|
||||
|
||||
if(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
set(INTEGRATION_TEST_SERVER_SOURCES ${INTEGRATION_TEST_SERVER_SOURCES}
|
||||
integration_test/server/api_v1_CoroTest.cc)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
endif(DROGON_CXX_STANDARD GREATER_EQUAL 20 AND HAS_COROUTINE)
|
||||
|
||||
set_property(TARGET ${test_targets}
|
||||
add_executable(integration_test_server ${INTEGRATION_TEST_SERVER_SOURCES})
|
||||
drogon_create_views(integration_test_server
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server
|
||||
${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_dependencies(integration_test_server drogon_ctl)
|
||||
add_custom_command(
|
||||
TARGET integration_test_server POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E
|
||||
copy_if_different
|
||||
${PROJECT_SOURCE_DIR}/config.example.json
|
||||
${PROJECT_SOURCE_DIR}/drogon.jpg
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/index.html
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/index.html.gz
|
||||
${PROJECT_SOURCE_DIR}/trantor/trantor/tests/server.pem
|
||||
$<TARGET_FILE_DIR:integration_test_server>)
|
||||
add_custom_command(
|
||||
TARGET integration_test_server POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-E
|
||||
copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/integration_test/server/a-directory
|
||||
$<TARGET_FILE_DIR:integration_test_server>/a-directory)
|
||||
|
||||
set(tests unittest integration_test_server integration_test_client)
|
||||
set_property(TARGET ${tests}
|
||||
PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD})
|
||||
set_property(TARGET ${test_targets} PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
set_property(TARGET ${test_targets} PROPERTY CXX_EXTENSIONS OFF)
|
||||
set_property(TARGET ${tests} PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
set_property(TARGET ${tests} PROPERTY CXX_EXTENSIONS OFF)
|
||||
|
||||
ParseAndAddDrogonTests(unittest)
|
@ -1,98 +0,0 @@
|
||||
#include <drogon/CacheMap.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
using namespace std::chrono_literals;
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
trantor::EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
|
||||
drogon::CacheMap<std::string, std::string> cache(loopThread.getLoop(),
|
||||
0.1,
|
||||
4,
|
||||
30);
|
||||
std::this_thread::sleep_for(3s);
|
||||
for (int i = 0; i < 40; ++i)
|
||||
{
|
||||
cache.insert(drogon::utils::formattedString("aaa%d", i),
|
||||
"hehe",
|
||||
i,
|
||||
[i]() {
|
||||
std::cout << i << " cache item erased!" << std::endl;
|
||||
});
|
||||
}
|
||||
cache.insert("1", "first", 20, [] {
|
||||
LOG_DEBUG << "first item in cache timeout,erase!";
|
||||
});
|
||||
cache.insert("2", "second", 5);
|
||||
cache.insert("3", "third", 5);
|
||||
std::thread thread1([&] {
|
||||
std::this_thread::sleep_for(1s);
|
||||
cache.erase("3");
|
||||
cache.insert("3", "THIRD");
|
||||
std::this_thread::sleep_for(10s);
|
||||
if (cache.find("1"))
|
||||
{
|
||||
LOG_DEBUG << "sleep 10,1 item:" << cache["1"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find 1 item";
|
||||
}
|
||||
std::this_thread::sleep_for(15s);
|
||||
if (cache.find("1"))
|
||||
{
|
||||
LOG_DEBUG << "sleep 15,1 item:" << cache["1"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find 1 item";
|
||||
}
|
||||
std::this_thread::sleep_for(20s);
|
||||
if (cache.find("1"))
|
||||
{
|
||||
LOG_DEBUG << "sleep 20,1 item:" << cache["1"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find 1 item";
|
||||
}
|
||||
std::this_thread::sleep_for(22s);
|
||||
if (cache.find("1"))
|
||||
{
|
||||
LOG_DEBUG << "sleep22,1 item:" << cache["1"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find 1 item";
|
||||
}
|
||||
|
||||
if (cache.find("2"))
|
||||
{
|
||||
LOG_DEBUG << "sleep22,2 item:" << cache["2"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find 2 item";
|
||||
}
|
||||
|
||||
if (cache.find("3"))
|
||||
{
|
||||
LOG_DEBUG << "sleep22,3 item:" << cache["3"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find 3 item";
|
||||
}
|
||||
});
|
||||
thread1.join();
|
||||
|
||||
getchar();
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
#include <drogon/CacheMap.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <trantor/utils/Logger.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::kTrace);
|
||||
trantor::EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
|
||||
auto loop = loopThread.getLoop();
|
||||
std::shared_ptr<drogon::CacheMap<std::string, std::string>> main_cachePtr;
|
||||
auto now = trantor::Date::date();
|
||||
loop->runAt(now.after(1).roundSecond(), [loop, &main_cachePtr]() {
|
||||
std::shared_ptr<drogon::CacheMap<std::string, std::string>> cachePtr =
|
||||
std::make_shared<drogon::CacheMap<std::string, std::string>>(loop,
|
||||
0.1,
|
||||
3,
|
||||
50);
|
||||
main_cachePtr = cachePtr;
|
||||
LOG_DEBUG << "insert :usecount=" << main_cachePtr.use_count();
|
||||
cachePtr->insert("1", "1", 3, []() {
|
||||
LOG_DEBUG << "timeout!erase 1!";
|
||||
});
|
||||
cachePtr->insert("2", "2", 10, []() { LOG_DEBUG << "2 timeout"; });
|
||||
});
|
||||
trantor::EventLoop mainLoop;
|
||||
mainLoop.runAt(now.after(4).roundSecond().after(0.1013), [&]() {
|
||||
main_cachePtr->insert("new", "new");
|
||||
if (main_cachePtr->find("1"))
|
||||
{
|
||||
LOG_DEBUG << "find item 1:" << (*main_cachePtr)["1"];
|
||||
//(*main_cachePtr)["1"] = "22";
|
||||
main_cachePtr->modify("1", [](std::string &item) { item = "22"; });
|
||||
LOG_DEBUG << (*main_cachePtr)["1"];
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_DEBUG << "can't find item 1";
|
||||
}
|
||||
});
|
||||
mainLoop.runAfter(8, [&]() { mainLoop.quit(); });
|
||||
mainLoop.loop();
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
#include <drogon/DrObject.h>
|
||||
#include <iostream>
|
||||
namespace api
|
||||
{
|
||||
namespace v1
|
||||
{
|
||||
template <typename T>
|
||||
class handler : public drogon::DrObject<T>
|
||||
{
|
||||
public:
|
||||
static void p()
|
||||
{
|
||||
std::cout << handler<T>::classTypeName() << std::endl;
|
||||
}
|
||||
};
|
||||
class hh : public handler<hh>
|
||||
{
|
||||
};
|
||||
} // namespace v1
|
||||
} // namespace api
|
||||
int main()
|
||||
{
|
||||
api::v1::hh h;
|
||||
std::cout << h.className() << std::endl;
|
||||
std::cout << api::v1::hh::classTypeName() << std::endl;
|
||||
h.p();
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
#include <drogon/Cookie.h>
|
||||
#include <iostream>
|
||||
int main()
|
||||
{
|
||||
drogon::Cookie cookie1("test", "1");
|
||||
std::cout << cookie1.cookieString();
|
||||
drogon::Cookie cookie2("test", "2");
|
||||
cookie2.setSecure(true);
|
||||
std::cout << cookie2.cookieString();
|
||||
drogon::Cookie cookie3("test", "3");
|
||||
cookie3.setDomain("drogon.org");
|
||||
cookie3.setExpiresDate(trantor::Date::date().after(3600 * 24));
|
||||
std::cout << cookie3.cookieString();
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
#include <drogon/utils/coroutine.h>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
namespace drogon::internal
|
||||
{
|
||||
struct SomeStruct
|
||||
{
|
||||
~SomeStruct()
|
||||
{
|
||||
beenDestructed = true;
|
||||
}
|
||||
static bool beenDestructed;
|
||||
};
|
||||
|
||||
bool SomeStruct::beenDestructed = false;
|
||||
|
||||
struct StructAwaiter : public CallbackAwaiter<std::shared_ptr<SomeStruct>>
|
||||
{
|
||||
void await_suspend(std::coroutine_handle<> handle)
|
||||
{
|
||||
setValue(std::make_shared<SomeStruct>());
|
||||
handle.resume();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace drogon::internal
|
||||
|
||||
int main()
|
||||
{
|
||||
// Basic checks making sure coroutine works as expected
|
||||
static_assert(is_awaitable_v<Task<>>);
|
||||
static_assert(is_awaitable_v<Task<int>>);
|
||||
static_assert(std::is_same_v<await_result_t<Task<int>>, int>);
|
||||
static_assert(std::is_same_v<await_result_t<Task<>>, void>);
|
||||
static_assert(is_awaitable_v<Task<>>);
|
||||
static_assert(is_awaitable_v<Task<int>>);
|
||||
|
||||
// No, you cannot await AsyncTask. By design
|
||||
static_assert(is_awaitable_v<AsyncTask> == false);
|
||||
|
||||
int n = 0;
|
||||
sync_wait([&]() -> Task<> {
|
||||
n = 1;
|
||||
co_return;
|
||||
}());
|
||||
if (n != 1)
|
||||
{
|
||||
std::cerr
|
||||
<< "Expected coroutine to change external valud. Didn't happen\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Make sure sync_wait works
|
||||
if (sync_wait([]() -> Task<int> { co_return 1; }()) != 1)
|
||||
{
|
||||
std::cerr << "Expected coroutine return 1. Didn't get that\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// co_future converts coroutine into futures. Doesn't work for now
|
||||
// as fut.get() waits for the coroutine. Yet the coroutine needs
|
||||
// the same thread to resume execution for it to return a value.
|
||||
// Thus causing a dead lock
|
||||
// auto fut = co_future([]() -> Task<std::string> { co_return "zxc"; }());
|
||||
// if (fut.get() != "zxc")
|
||||
// {
|
||||
// std::cerr << "Expected future return \'zxc\'. Didn't get that\n";
|
||||
// exit(1);
|
||||
// }
|
||||
|
||||
// Testing that exceptions can propergate through coroutines
|
||||
auto throw_in_task = []() -> Task<> {
|
||||
auto f = []() -> Task<> { throw std::runtime_error("test error"); };
|
||||
|
||||
try
|
||||
{
|
||||
co_await f();
|
||||
std::cerr << "Exception should have been thrown\n";
|
||||
exit(1);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
if (std::string(e.what()) != "test error")
|
||||
{
|
||||
std::cerr << "Not the right exception\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "Shouldn't reach here\n";
|
||||
exit(1);
|
||||
}
|
||||
co_return;
|
||||
};
|
||||
sync_wait(throw_in_task());
|
||||
|
||||
// Test coroutine destruction
|
||||
auto destruct = []() -> Task<> {
|
||||
auto awaitStruct = []() -> Task<std::shared_ptr<internal::SomeStruct>> {
|
||||
co_return co_await internal::StructAwaiter();
|
||||
};
|
||||
|
||||
auto awaitNothing = [awaitStruct]() -> Task<> {
|
||||
co_await awaitStruct();
|
||||
};
|
||||
|
||||
co_await awaitNothing();
|
||||
};
|
||||
sync_wait(destruct());
|
||||
if (internal::SomeStruct::beenDestructed == false)
|
||||
{
|
||||
std::cerr << "Coroutine didn't destruct allocated object.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
std::cout << "Done testing coroutines. No error." << std::endl;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
#include <drogon/HttpViewData.h>
|
||||
#include <iostream>
|
||||
int main()
|
||||
{
|
||||
drogon::HttpViewData data;
|
||||
std::cout << (data.insert("1", 1), data.get<int>("1")) << std::endl;
|
||||
std::cout << (data.insertAsString("2", 2.0), data.get<std::string>("2"))
|
||||
<< std::endl;
|
||||
std::cout << (data.insertFormattedString("3", "third value is %d", 3),
|
||||
data.get<std::string>("3"))
|
||||
<< std::endl;
|
||||
std::cout << (data.insertAsString("4", "4"), data.get<std::string>("4"))
|
||||
<< std::endl;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
#include <drogon/drogon.h>
|
||||
#include <thread>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::thread([]() {
|
||||
drogon::app().getLoop()->runEvery(1, []() {
|
||||
std::cout << "!" << std::endl;
|
||||
});
|
||||
}).detach();
|
||||
drogon::app().run();
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
#include <drogon/drogon.h>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* @brief This test program tests to call the app().run() in another thread.
|
||||
*
|
||||
*/
|
||||
int main()
|
||||
{
|
||||
std::thread([]() {
|
||||
drogon::app().getLoop()->runEvery(1, []() {
|
||||
std::cout << "!" << std::endl;
|
||||
});
|
||||
}).detach();
|
||||
|
||||
std::thread l([]() { drogon::app().run(); });
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
trantor::EventLoop loop;
|
||||
l.join();
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#include <drogon/utils/string_view.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::string input = "k1=1&k2=安";
|
||||
auto output = drogon::utils::urlEncode(input);
|
||||
std::cout << output << std::endl;
|
||||
auto output2 = drogon::utils::urlDecode(output);
|
||||
std::cout << output2 << std::endl;
|
||||
std::cout << drogon::utils::urlEncode("k2=安&k1=1") << std::endl;
|
||||
std::cout << drogon::utils::urlDecode(
|
||||
drogon::string_view("k2%3D%E5%AE%89&k1%3D1"))
|
||||
<< std::endl;
|
||||
return 0;
|
||||
}
|
58
lib/tests/integration_test/client/HttpPipeliningTest.cc
Normal file
58
lib/tests/integration_test/client/HttpPipeliningTest.cc
Normal file
@ -0,0 +1,58 @@
|
||||
#include <drogon/HttpClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <atomic>
|
||||
using namespace drogon;
|
||||
|
||||
static int counter = -1;
|
||||
|
||||
DROGON_TEST(HttpPipeliningTest)
|
||||
{
|
||||
auto client = HttpClient::newHttpClient("127.0.0.1", 8848);
|
||||
client->setPipeliningDepth(64);
|
||||
|
||||
auto request1 = HttpRequest::newHttpRequest();
|
||||
request1->setPath("/pipe");
|
||||
request1->setMethod(Head);
|
||||
|
||||
client->sendRequest(
|
||||
request1, [TEST_CTX](ReqResult r, const HttpResponsePtr &resp) {
|
||||
REQUIRE(r == ReqResult::Ok);
|
||||
auto counterHeader = resp->getHeader("counter");
|
||||
int c = atoi(counterHeader.data());
|
||||
if (c <= counter)
|
||||
FAIL("The response was received in the wrong order!");
|
||||
else
|
||||
SUCCESS();
|
||||
counter = c;
|
||||
REQUIRE(resp->body().empty());
|
||||
});
|
||||
|
||||
auto request2 = HttpRequest::newHttpRequest();
|
||||
request2->setPath("/drogon.jpg");
|
||||
client->sendRequest(request2,
|
||||
[TEST_CTX](ReqResult r, const HttpResponsePtr &resp) {
|
||||
REQUIRE(r == ReqResult::Ok);
|
||||
REQUIRE(resp->getBody().length() == 44618);
|
||||
});
|
||||
|
||||
auto request = HttpRequest::newHttpRequest();
|
||||
request->setPath("/pipe");
|
||||
for (int i = 0; i < 19; ++i)
|
||||
{
|
||||
client->sendRequest(
|
||||
request1, [TEST_CTX](ReqResult r, const HttpResponsePtr &resp) {
|
||||
REQUIRE(r == ReqResult::Ok);
|
||||
auto counterHeader = resp->getHeader("counter");
|
||||
int c = atoi(counterHeader.data());
|
||||
if (c <= counter)
|
||||
FAIL("The response was received in the wrong order!");
|
||||
else
|
||||
SUCCESS();
|
||||
counter = c;
|
||||
REQUIRE(resp->body().empty());
|
||||
});
|
||||
}
|
||||
}
|
55
lib/tests/integration_test/client/MultipleWsTest.cc
Normal file
55
lib/tests/integration_test/client/MultipleWsTest.cc
Normal file
@ -0,0 +1,55 @@
|
||||
#include <drogon/WebSocketClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static std::vector<WebSocketClientPtr> wsClients_;
|
||||
|
||||
DROGON_TEST(MultipleWsTest)
|
||||
{
|
||||
// NOTE: The original test was for 1000 clients. But that seems to be
|
||||
// causing memory leak.
|
||||
wsClients_.reserve(20);
|
||||
for (size_t i = 0; i < 20; i++)
|
||||
{
|
||||
auto wsPtr = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/chat");
|
||||
wsPtr->setMessageHandler(
|
||||
[TEST_CTX, i](const std::string &message,
|
||||
const WebSocketClientPtr &wsPtr,
|
||||
const WebSocketMessageType &type) mutable {
|
||||
CHECK((type == WebSocketMessageType::Text ||
|
||||
type == WebSocketMessageType::Pong));
|
||||
if (type == WebSocketMessageType::Pong && TEST_CTX != nullptr)
|
||||
{
|
||||
// Check if the correct connection got the result
|
||||
CHECK(message == std::to_string(i));
|
||||
TEST_CTX = {};
|
||||
}
|
||||
});
|
||||
|
||||
wsPtr->connectToServer(
|
||||
req,
|
||||
[TEST_CTX, i](ReqResult r,
|
||||
const HttpResponsePtr &resp,
|
||||
const WebSocketClientPtr &wsPtr) mutable {
|
||||
CHECK(r == ReqResult::Ok);
|
||||
if (r != ReqResult::Ok)
|
||||
app().getLoop()->queueInLoop([i]() { wsClients_[i] = {}; });
|
||||
REQUIRE(wsPtr != nullptr);
|
||||
REQUIRE(resp != nullptr);
|
||||
|
||||
wsPtr->getConnection()->setPingMessage(std::to_string(i), 1.5s);
|
||||
wsPtr->getConnection()->send("hello!");
|
||||
CHECK(wsPtr->getConnection()->connected());
|
||||
|
||||
TEST_CTX = {};
|
||||
});
|
||||
wsClients_.emplace_back(std::move(wsPtr));
|
||||
}
|
||||
}
|
48
lib/tests/integration_test/client/WebSocketTest.cc
Normal file
48
lib/tests/integration_test/client/WebSocketTest.cc
Normal file
@ -0,0 +1,48 @@
|
||||
#include <drogon/WebSocketClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
static WebSocketClientPtr wsPtr_;
|
||||
DROGON_TEST(WebSocketTest)
|
||||
{
|
||||
wsPtr_ = WebSocketClient::newWebSocketClient("127.0.0.1", 8848);
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/chat");
|
||||
wsPtr_->setMessageHandler(
|
||||
[TEST_CTX](const std::string &message,
|
||||
const WebSocketClientPtr &wsPtr,
|
||||
const WebSocketMessageType &type) mutable {
|
||||
CHECK((type == WebSocketMessageType::Text ||
|
||||
type == WebSocketMessageType::Pong));
|
||||
if (type == WebSocketMessageType::Pong)
|
||||
{
|
||||
CHECK(message.empty());
|
||||
TEST_CTX = {};
|
||||
}
|
||||
});
|
||||
|
||||
wsPtr_->connectToServer(
|
||||
req,
|
||||
[TEST_CTX](ReqResult r,
|
||||
const HttpResponsePtr &resp,
|
||||
const WebSocketClientPtr &wsPtr) mutable {
|
||||
if (r != ReqResult::Ok)
|
||||
app().getLoop()->queueInLoop([]() { wsPtr_ = {}; });
|
||||
REQUIRE(r == ReqResult::Ok);
|
||||
REQUIRE(wsPtr != nullptr);
|
||||
REQUIRE(resp != nullptr);
|
||||
|
||||
wsPtr->getConnection()->setPingMessage("", 1s);
|
||||
wsPtr->getConnection()->send("hello!");
|
||||
CHECK(wsPtr->getConnection()->connected());
|
||||
|
||||
// Drop the testing context as WS controllers stores the lambda and
|
||||
// never release it. Causing a dead lock later.
|
||||
TEST_CTX = {};
|
||||
});
|
||||
}
|
740
lib/tests/integration_test/client/main.cc
Normal file
740
lib/tests/integration_test/client/main.cc
Normal file
@ -0,0 +1,740 @@
|
||||
/**
|
||||
*
|
||||
* @file test.cc
|
||||
* @author An Tao
|
||||
*
|
||||
* Copyright 2018, An Tao. All rights reserved.
|
||||
* https://github.com/an-tao/drogon
|
||||
* Use of this source code is governed by a MIT license
|
||||
* that can be found in the License file.
|
||||
*
|
||||
* Drogon
|
||||
*
|
||||
*/
|
||||
|
||||
// Make a http client to test the example server app;
|
||||
|
||||
#define DROGON_TEST_MAIN
|
||||
#include <drogon/drogon.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <trantor/net/TcpClient.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define JPG_LEN 44618
|
||||
size_t indexLen;
|
||||
size_t indexImplicitLen;
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
Cookie sessionID;
|
||||
|
||||
void doTest(const HttpClientPtr &client, std::shared_ptr<test::Case> TEST_CTX)
|
||||
{
|
||||
/// Get cookie
|
||||
if (!sessionID)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/");
|
||||
std::promise<int> waitCookie;
|
||||
auto f = waitCookie.get_future();
|
||||
client->sendRequest(req,
|
||||
[client, &waitCookie, TEST_CTX](
|
||||
ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
auto &id = resp->getCookie("JSESSIONID");
|
||||
REQUIRE(id);
|
||||
|
||||
sessionID = id;
|
||||
client->addCookie(id);
|
||||
waitCookie.set_value(1);
|
||||
});
|
||||
f.get();
|
||||
}
|
||||
else
|
||||
client->addCookie(sessionID);
|
||||
|
||||
/// Test session
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/slow");
|
||||
client->sendRequest(
|
||||
req,
|
||||
[req, client, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
auto req1 = HttpRequest::newHttpRequest();
|
||||
req1->setMethod(drogon::Get);
|
||||
req1->setPath("/slow");
|
||||
client->sendRequest(
|
||||
req1,
|
||||
[req1, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp1) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
auto &json = resp1->jsonObject();
|
||||
REQUIRE(json != nullptr);
|
||||
REQUIRE(json->get("message", std::string("")).asString() ==
|
||||
"Access interval should be "
|
||||
"at least 10 "
|
||||
"seconds");
|
||||
});
|
||||
});
|
||||
/// Test gzip
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->addHeader("accept-encoding", "gzip");
|
||||
req->setPath("/api/v1/apitest/get/111");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == 4994);
|
||||
});
|
||||
/// Test brotli
|
||||
#ifdef USE_BROTLI
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->addHeader("accept-encoding", "br");
|
||||
req->setPath("/api/v1/apitest/get/111");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == 4994);
|
||||
});
|
||||
#endif
|
||||
/// Post json
|
||||
Json::Value json;
|
||||
json["request"] = "json";
|
||||
req = HttpRequest::newCustomHttpRequest(json);
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/api/v1/apitest/json");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
std::shared_ptr<Json::Value> ret = *resp;
|
||||
REQUIRE(resp != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
// Post json again
|
||||
req = HttpRequest::newHttpJsonRequest(json);
|
||||
assert(req->jsonObject());
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/api/v1/apitest/json");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
std::shared_ptr<Json::Value> ret = *resp;
|
||||
REQUIRE(resp != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
// Post json again
|
||||
req = HttpRequest::newHttpJsonRequest(json);
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/api/v1/apitest/json");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
|
||||
std::shared_ptr<Json::Value> ret = *resp;
|
||||
REQUIRE(resp != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
// Test 404
|
||||
req = HttpRequest::newHttpJsonRequest(json);
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest/notFoundRouting");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k404NotFound);
|
||||
});
|
||||
/// 1 Get /
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "<p>Hello, world!</p>");
|
||||
// LOG_DEBUG << resp->getBody();
|
||||
});
|
||||
/// 3. Post to /tpost to test Http Method constraint
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/tpost");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k405MethodNotAllowed);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/tpost");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "<p>Hello, world!</p>");
|
||||
});
|
||||
|
||||
/// 4. Http OPTIONS Method
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Options);
|
||||
req->setPath("/tpost");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
auto allow = resp->getHeader("allow");
|
||||
CHECK(allow.find("POST") != std::string::npos);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Options);
|
||||
req->setPath("/api/v1/apitest");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
auto allow = resp->getHeader("allow");
|
||||
CHECK(allow == "OPTIONS,GET,HEAD,POST");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Options);
|
||||
req->setPath("/slow");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k403Forbidden);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Options);
|
||||
req->setPath("/*");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
auto allow = resp->getHeader("allow");
|
||||
CHECK(allow == "GET,HEAD,POST,PUT,DELETE,OPTIONS,PATCH");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Options);
|
||||
req->setPath("/api/v1/apitest/static");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
auto allow = resp->getHeader("allow");
|
||||
CHECK(allow == "OPTIONS,GET,HEAD");
|
||||
});
|
||||
|
||||
/// 4. Test HttpController
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/api/v1/apitest");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "ROOT Post!!!");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "ROOT Get!!!");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest/get/abc/123");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto body = resp->getBody();
|
||||
CHECK(body.find("<td>p1</td>\n <td>123</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>p2</td>\n <td>abc</td>") !=
|
||||
std::string::npos);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest/3.14/List");
|
||||
req->setParameter("P2", "1234");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto body = resp->getBody();
|
||||
CHECK(body.find("<td>p1</td>\n <td>3.140000</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>p2</td>\n <td>1234</td>") !=
|
||||
std::string::npos);
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/reg/123/rest/of/the/path");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
REQUIRE(resp->getJsonObject() != nullptr);
|
||||
|
||||
auto &json = resp->getJsonObject();
|
||||
CHECK(json->isMember("p1"));
|
||||
CHECK(json->get("p1", 0).asInt() == 123);
|
||||
CHECK(json->isMember("p2"));
|
||||
CHECK(json->get("p2", "").asString() == "rest/of/the/path");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest/static");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "staticApi,hello!!");
|
||||
});
|
||||
|
||||
// auto loop = app().loop();
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/api/v1/apitest/static");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "staticApi,hello!!");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest/get/111");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == 4994);
|
||||
});
|
||||
|
||||
/// Test static function
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/handle11/11/2 2/?p3=3 x");
|
||||
req->setParameter("p4", "44");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto body = resp->getBody();
|
||||
CHECK(body.find("<td>int p1</td>\n <td>11</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>int p4</td>\n <td>44</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>string p2</td>\n <td>2 2</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>string p3</td>\n <td>3 x</td>") !=
|
||||
std::string::npos);
|
||||
});
|
||||
/// Test Incomplete URL
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/handle11/11/2 2/");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto body = resp->getBody();
|
||||
CHECK(body.find("<td>int p1</td>\n <td>11</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>int p4</td>\n <td>0</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>string p2</td>\n <td>2 2</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>string p3</td>\n <td></td>") !=
|
||||
std::string::npos);
|
||||
});
|
||||
/// Test lambda
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/handle2/111/222");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto body = resp->getBody();
|
||||
CHECK(body.find("<td>a</td>\n <td>111</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>b</td>\n <td>222.000000</td>") !=
|
||||
std::string::npos);
|
||||
});
|
||||
|
||||
/// Test std::bind and std::function
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/handle4/444/333/111");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto body = resp->getBody();
|
||||
CHECK(body.find("<td>int p1</td>\n <td>111</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>int p4</td>\n <td>444</td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>string p2</td>\n <td></td>") !=
|
||||
std::string::npos);
|
||||
CHECK(body.find("<td>string p3</td>\n <td>333</td>") !=
|
||||
std::string::npos);
|
||||
});
|
||||
/// Test gzip_static
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/index.html");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == indexLen);
|
||||
});
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/index.html");
|
||||
req->addHeader("accept-encoding", "gzip");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == indexLen);
|
||||
});
|
||||
// Test 405
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Post);
|
||||
req->setPath("/drogon.jpg");
|
||||
client->sendRequest(req,
|
||||
[req, client, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() ==
|
||||
k405MethodNotAllowed);
|
||||
});
|
||||
/// Test file download
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/drogon.jpg");
|
||||
client->sendRequest(
|
||||
req,
|
||||
[req, client, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
REQUIRE(resp->getBody().length() == JPG_LEN);
|
||||
auto &lastModified = resp->getHeader("last-modified");
|
||||
// LOG_DEBUG << lastModified;
|
||||
// Test 'Not Modified'
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/drogon.jpg");
|
||||
req->addHeader("If-Modified-Since", lastModified);
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
REQUIRE(resp->statusCode() ==
|
||||
k304NotModified);
|
||||
// pro.set_value(1);
|
||||
});
|
||||
});
|
||||
|
||||
/// Test file download, It is forbidden to download files from the
|
||||
/// parent folder
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/../../drogon.jpg");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k403Forbidden);
|
||||
});
|
||||
/// Test controllers created and initialized by users
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/customctrl/antao");
|
||||
req->addHeader("custom_header", "yes");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "<P>Hi, antao</P>");
|
||||
});
|
||||
/// Test controllers created and initialized by users
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/absolute/123");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->statusCode() == k200OK);
|
||||
});
|
||||
/// Test synchronous advice
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/plaintext");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody() == "Hello, World!");
|
||||
});
|
||||
/// Test form post
|
||||
req = HttpRequest::newHttpFormPostRequest();
|
||||
req->setPath("/api/v1/apitest/form");
|
||||
req->setParameter("k1", "1");
|
||||
req->setParameter("k2", "安");
|
||||
req->setParameter("k3", "test@example.com");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto ret = resp->getJsonObject();
|
||||
CHECK(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
/// Test attributes
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/apitest/attrs");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto ret = resp->getJsonObject();
|
||||
CHECK(ret != nullptr);
|
||||
CHECK((*ret)["result"].asString() == "ok");
|
||||
});
|
||||
|
||||
/// Test attachment download
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/attachment/download");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == JPG_LEN);
|
||||
});
|
||||
// Test implicit pages
|
||||
auto body = std::make_shared<std::string>();
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/a-directory");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX, body](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == indexImplicitLen);
|
||||
*body = std::string(resp->getBody().data(),
|
||||
resp->getBody().length());
|
||||
});
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/a-directory/page.html");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX, body](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getBody().length() == indexImplicitLen);
|
||||
CHECK(std::equal(body->begin(),
|
||||
body->end(),
|
||||
resp->getBody().begin()));
|
||||
});
|
||||
// return;
|
||||
// Test file upload
|
||||
UploadFile file1("./drogon.jpg");
|
||||
UploadFile file2("./drogon.jpg", "drogon1.jpg");
|
||||
UploadFile file3("./config.example.json", "config.json", "file3");
|
||||
req = HttpRequest::newFileUploadRequest({file1, file2, file3});
|
||||
req->setPath("/api/attachment/upload");
|
||||
req->setParameter("P1", "upload");
|
||||
req->setParameter("P2", "test");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto json = resp->getJsonObject();
|
||||
CHECK(json != nullptr);
|
||||
CHECK((*json)["result"].asString() == "ok");
|
||||
CHECK((*json)["P1"] == "upload");
|
||||
CHECK((*json)["P2"] == "test");
|
||||
});
|
||||
|
||||
// return;
|
||||
// Test file upload, file type and extension interface.
|
||||
UploadFile image("./drogon.jpg");
|
||||
req = HttpRequest::newFileUploadRequest({image});
|
||||
req->setPath("/api/attachment/uploadImage");
|
||||
req->setParameter("P1", "upload");
|
||||
req->setParameter("P2", "test");
|
||||
client->sendRequest(req,
|
||||
[req, TEST_CTX](ReqResult result,
|
||||
const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
auto json = resp->getJsonObject();
|
||||
CHECK(json != nullptr);
|
||||
CHECK((*json)["P1"] == "upload");
|
||||
CHECK((*json)["P2"] == "test");
|
||||
});
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Get);
|
||||
req->setPath("/api/v1/this_will_fail");
|
||||
client->sendRequest(
|
||||
req, [req, TEST_CTX](ReqResult result, const HttpResponsePtr &resp) {
|
||||
REQUIRE(result == ReqResult::Ok);
|
||||
CHECK(resp->getStatusCode() == k500InternalServerError);
|
||||
});
|
||||
|
||||
#if defined(__cpp_impl_coroutine)
|
||||
sync_wait([client, TEST_CTX]() -> Task<> {
|
||||
// Test coroutine requests
|
||||
try
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/api/v1/corotest/get");
|
||||
auto resp = co_await client->sendRequestCoro(req);
|
||||
CHECK(resp->getBody() == "DEADBEEF");
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
FAIL("Unexpected exception, what()" + std::string(e.what()));
|
||||
}
|
||||
|
||||
// Test coroutine request with co_return
|
||||
try
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/api/v1/corotest/get2");
|
||||
auto resp = co_await client->sendRequestCoro(req);
|
||||
CHECK(resp->getBody() == "BADDBEEF");
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
FAIL("Unexpected exception, what()" + std::string(e.what()));
|
||||
}
|
||||
|
||||
// Test Coroutine exception
|
||||
try
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/api/v1/corotest/this_will_fail");
|
||||
auto resp = co_await client->sendRequestCoro(req);
|
||||
CHECK(resp->getStatusCode() != k200OK);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
FAIL("Unexpected exception, what()" + std::string(e.what()));
|
||||
}
|
||||
|
||||
// Test Coroutine exception with co_return
|
||||
try
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setPath("/api/v1/corotest/this_will_fail2");
|
||||
auto resp = co_await client->sendRequestCoro(req);
|
||||
CHECK(resp->getStatusCode() != k200OK);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
FAIL("Unexpected exception, what()" + std::string(e.what()));
|
||||
}
|
||||
}());
|
||||
#endif
|
||||
}
|
||||
|
||||
void loadFileLengths()
|
||||
{
|
||||
struct stat filestat;
|
||||
if (stat("index.html", &filestat) < 0)
|
||||
{
|
||||
LOG_SYSERR << "Unable to retrieve index.html file sizes";
|
||||
exit(1);
|
||||
}
|
||||
indexLen = filestat.st_size;
|
||||
if (stat("a-directory/page.html", &filestat) < 0)
|
||||
{
|
||||
LOG_SYSERR << "Unable to retrieve a-directory/page.html file sizes";
|
||||
exit(1);
|
||||
}
|
||||
indexImplicitLen = filestat.st_size;
|
||||
}
|
||||
|
||||
DROGON_TEST(HttpTest)
|
||||
{
|
||||
auto client = HttpClient::newHttpClient("http://127.0.0.1:8848");
|
||||
client->setPipeliningDepth(10);
|
||||
doTest(client, TEST_CTX);
|
||||
}
|
||||
|
||||
DROGON_TEST(HttpsTest)
|
||||
{
|
||||
if (!app().supportSSL())
|
||||
return;
|
||||
|
||||
auto client = HttpClient::newHttpClient("https://127.0.0.1:8849",
|
||||
app().getLoop(),
|
||||
false,
|
||||
false);
|
||||
client->setPipeliningDepth(10);
|
||||
doTest(client, TEST_CTX);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
trantor::Logger::setLogLevel(trantor::Logger::LogLevel::kDebug);
|
||||
loadFileLengths();
|
||||
|
||||
std::promise<void> p1;
|
||||
std::future<void> f1 = p1.get_future();
|
||||
|
||||
std::thread thr([&]() {
|
||||
p1.set_value();
|
||||
app().run();
|
||||
});
|
||||
|
||||
f1.get();
|
||||
int testStatus = test::run(argc, argv);
|
||||
app().getLoop()->queueInLoop([]() { app().quit(); });
|
||||
thr.join();
|
||||
return testStatus;
|
||||
}
|
0
examples/simple_example/ListParaView.csp → lib/tests/integration_test/server/ListParaView.csp
Executable file → Normal file
0
examples/simple_example/ListParaView.csp → lib/tests/integration_test/server/ListParaView.csp
Executable file → Normal file
0
examples/simple_example/TestView.csp → lib/tests/integration_test/server/TestView.csp
Executable file → Normal file
0
examples/simple_example/TestView.csp → lib/tests/integration_test/server/TestView.csp
Executable file → Normal file
0
examples/simple_example/TimeFilter.h → lib/tests/integration_test/server/TimeFilter.h
Executable file → Normal file
0
examples/simple_example/TimeFilter.h → lib/tests/integration_test/server/TimeFilter.h
Executable file → Normal file
@ -21,3 +21,17 @@ Task<HttpResponsePtr> CoroTest::get2(HttpRequestPtr req)
|
||||
resp->setBody("BADDBEEF");
|
||||
co_return resp;
|
||||
}
|
||||
|
||||
Task<> CoroTest::this_will_fail(
|
||||
HttpRequestPtr req,
|
||||
std::function<void(const HttpResponsePtr &)> callback)
|
||||
{
|
||||
throw std::runtime_error("This is an excpected exception");
|
||||
callback(HttpResponse::newHttpResponse());
|
||||
}
|
||||
|
||||
Task<HttpResponsePtr> CoroTest::this_will_fail2(HttpRequestPtr req)
|
||||
{
|
||||
throw std::runtime_error("This is an excpected exception");
|
||||
co_return HttpResponse::newHttpResponse();
|
||||
}
|
@ -11,11 +11,17 @@ class CoroTest : public drogon::HttpController<CoroTest>
|
||||
METHOD_LIST_BEGIN
|
||||
METHOD_ADD(CoroTest::get, "/get", Get);
|
||||
METHOD_ADD(CoroTest::get2, "/get2", Get);
|
||||
METHOD_ADD(CoroTest::this_will_fail, "/this_will_fail", Get);
|
||||
METHOD_ADD(CoroTest::this_will_fail2, "/this_will_fail2", Get);
|
||||
METHOD_LIST_END
|
||||
|
||||
Task<> get(HttpRequestPtr req,
|
||||
std::function<void(const HttpResponsePtr &)> callback);
|
||||
Task<HttpResponsePtr> get2(HttpRequestPtr req);
|
||||
Task<> this_will_fail(
|
||||
HttpRequestPtr req,
|
||||
std::function<void(const HttpResponsePtr &)> callback);
|
||||
Task<HttpResponsePtr> this_will_fail2(HttpRequestPtr req);
|
||||
};
|
||||
} // namespace v1
|
||||
} // namespace api
|
0
examples/simple_example/index.html → lib/tests/integration_test/server/index.html
Executable file → Normal file
0
examples/simple_example/index.html → lib/tests/integration_test/server/index.html
Executable file → Normal file
59
lib/tests/unittests/Base64Test.cc
Normal file
59
lib/tests/unittests/Base64Test.cc
Normal file
@ -0,0 +1,59 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
|
||||
DROGON_TEST(Base64)
|
||||
{
|
||||
std::string in{"drogon framework"};
|
||||
auto encoded = drogon::utils::base64Encode((const unsigned char *)in.data(),
|
||||
in.length());
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
|
||||
SUBSECTION(LongString)
|
||||
{
|
||||
std::string in;
|
||||
in.reserve(100000);
|
||||
for (int i = 0; i < 100000; ++i)
|
||||
{
|
||||
in.append(1, char(i));
|
||||
}
|
||||
auto out = drogon::utils::base64Encode((const unsigned char *)in.data(),
|
||||
in.length());
|
||||
auto out2 = drogon::utils::base64Decode(out);
|
||||
auto encoded =
|
||||
drogon::utils::base64Encode((const unsigned char *)in.data(),
|
||||
in.length());
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
|
||||
SUBSECTION(URLSafe)
|
||||
{
|
||||
std::string in{"drogon framework"};
|
||||
auto encoded =
|
||||
drogon::utils::base64Encode((const unsigned char *)in.data(),
|
||||
in.length(),
|
||||
true);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
|
||||
SUBSECTION(LongURLSafe)
|
||||
{
|
||||
std::string in;
|
||||
in.reserve(100000);
|
||||
for (int i = 0; i < 100000; ++i)
|
||||
{
|
||||
in.append(1, char(i));
|
||||
}
|
||||
auto encoded =
|
||||
drogon::utils::base64Encode((const unsigned char *)in.data(),
|
||||
in.length(),
|
||||
true);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
}
|
29
lib/tests/unittests/BrotliTest.cc
Normal file
29
lib/tests/unittests/BrotliTest.cc
Normal file
@ -0,0 +1,29 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
using namespace drogon::utils;
|
||||
DROGON_TEST(BrotliTest)
|
||||
{
|
||||
SUBSECTION(shortText)
|
||||
{
|
||||
std::string source{"123中文顶替要枯械"};
|
||||
auto compressed = brotliCompress(source.data(), source.length());
|
||||
auto decompressed =
|
||||
brotliDecompress(compressed.data(), compressed.length());
|
||||
CHECK(source == decompressed);
|
||||
}
|
||||
|
||||
SUBSECTION(longText)
|
||||
{
|
||||
std::string source;
|
||||
for (size_t i = 0; i < 100000; i++)
|
||||
{
|
||||
source.append(std::to_string(i));
|
||||
}
|
||||
auto compressed = brotliCompress(source.data(), source.length());
|
||||
auto decompressed =
|
||||
brotliDecompress(compressed.data(), compressed.length());
|
||||
CHECK(source == decompressed);
|
||||
}
|
||||
}
|
41
lib/tests/unittests/CacheMapTest.cc
Normal file
41
lib/tests/unittests/CacheMapTest.cc
Normal file
@ -0,0 +1,41 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/CacheMap.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
DROGON_TEST(CacheMapTest)
|
||||
{
|
||||
trantor::EventLoopThread loopThread;
|
||||
loopThread.run();
|
||||
drogon::CacheMap<std::string, std::string> cache(loopThread.getLoop(),
|
||||
0.1,
|
||||
4,
|
||||
30);
|
||||
|
||||
for (size_t i = 1; i < 40; i++)
|
||||
cache.insert(std::to_string(i), "a", i);
|
||||
cache.insert("bla", "");
|
||||
cache.insert("zzz", "-");
|
||||
std::this_thread::sleep_for(3s);
|
||||
CHECK(cache.find("0") == false); // doesn't exist
|
||||
CHECK(cache.find("1") == false); // timeout
|
||||
CHECK(cache.find("15") == true);
|
||||
CHECK(cache.find("bla") == true);
|
||||
|
||||
cache.erase("30");
|
||||
CHECK(cache.find("30") == false);
|
||||
|
||||
cache.modify("bla", [](std::string& s) { s = "asd"; });
|
||||
CHECK(cache["bla"] == "asd");
|
||||
|
||||
std::string content;
|
||||
cache.findAndFetch("zzz", content);
|
||||
CHECK(content == "-");
|
||||
|
||||
loopThread.getLoop()->quit();
|
||||
}
|
28
lib/tests/unittests/ClassNameTest.cc
Normal file
28
lib/tests/unittests/ClassNameTest.cc
Normal file
@ -0,0 +1,28 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
namespace api
|
||||
{
|
||||
namespace v1
|
||||
{
|
||||
template <typename T>
|
||||
class handler : public drogon::DrObject<T>
|
||||
{
|
||||
public:
|
||||
static std::string name()
|
||||
{
|
||||
return handler<T>::classTypeName();
|
||||
}
|
||||
};
|
||||
class hh : public handler<hh>
|
||||
{
|
||||
};
|
||||
} // namespace v1
|
||||
} // namespace api
|
||||
|
||||
DROGON_TEST(ClassName)
|
||||
{
|
||||
api::v1::hh h;
|
||||
CHECK(h.className() == "api::v1::hh");
|
||||
CHECK(api::v1::hh::classTypeName() == "api::v1::hh");
|
||||
CHECK(h.name() == "api::v1::hh");
|
||||
}
|
19
lib/tests/unittests/CookieTest.cc
Normal file
19
lib/tests/unittests/CookieTest.cc
Normal file
@ -0,0 +1,19 @@
|
||||
#include <drogon/Cookie.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
DROGON_TEST(CookieTest)
|
||||
{
|
||||
drogon::Cookie cookie1("test", "1");
|
||||
CHECK(cookie1.cookieString() == "Set-Cookie: test=1; HttpOnly\r\n");
|
||||
|
||||
drogon::Cookie cookie2("test", "2");
|
||||
cookie2.setSecure(true);
|
||||
CHECK(cookie2.cookieString() == "Set-Cookie: test=2; Secure; HttpOnly\r\n");
|
||||
|
||||
drogon::Cookie cookie3("test", "3");
|
||||
cookie3.setDomain("drogon.org");
|
||||
cookie3.setExpiresDate(trantor::Date(1621561557000000L));
|
||||
CHECK(cookie3.cookieString() ==
|
||||
"Set-Cookie: test=3; Expires=Fri, 21 May 2021 01:45:57 GMT; "
|
||||
"Domain=drogon.org; HttpOnly\r\n");
|
||||
}
|
141
lib/tests/unittests/CoroutineTest.cc
Normal file
141
lib/tests/unittests/CoroutineTest.cc
Normal file
@ -0,0 +1,141 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/utils/coroutine.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
namespace drogon::internal
|
||||
{
|
||||
struct SomeStruct
|
||||
{
|
||||
~SomeStruct()
|
||||
{
|
||||
beenDestructed = true;
|
||||
}
|
||||
static bool beenDestructed;
|
||||
};
|
||||
|
||||
bool SomeStruct::beenDestructed = false;
|
||||
|
||||
struct StructAwaiter : public CallbackAwaiter<std::shared_ptr<SomeStruct>>
|
||||
{
|
||||
void await_suspend(std::coroutine_handle<> handle)
|
||||
{
|
||||
setValue(std::make_shared<SomeStruct>());
|
||||
handle.resume();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace drogon::internal
|
||||
|
||||
// Workarround limitation of macros
|
||||
template <typename T>
|
||||
using is_int = std::is_same<T, int>;
|
||||
template <typename T>
|
||||
using is_void = std::is_same<T, void>;
|
||||
|
||||
DROGON_TEST(CroutineBasics)
|
||||
{
|
||||
// Basic checks making sure coroutine works as expected
|
||||
STATIC_REQUIRE(is_awaitable_v<Task<>>);
|
||||
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
|
||||
STATIC_REQUIRE(is_awaitable_v<Task<>>);
|
||||
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
|
||||
STATIC_REQUIRE(is_int<await_result_t<Task<int>>>::value);
|
||||
STATIC_REQUIRE(is_void<await_result_t<Task<>>>::value);
|
||||
|
||||
// No, you cannot await AsyncTask. By design
|
||||
STATIC_REQUIRE(is_awaitable_v<AsyncTask> == false);
|
||||
|
||||
// AsyncTask should execute eagerly
|
||||
int m = 0;
|
||||
[&m]() -> AsyncTask {
|
||||
m = 1;
|
||||
co_return;
|
||||
}();
|
||||
REQUIRE(m == 1);
|
||||
|
||||
// Make sure sync_wait works
|
||||
CHECK(sync_wait([]() -> Task<int> { co_return 1; }()) == 1);
|
||||
|
||||
// make sure it does affect the outside world
|
||||
int n = 0;
|
||||
sync_wait([&]() -> Task<> {
|
||||
n = 1;
|
||||
co_return;
|
||||
}());
|
||||
CHECK(n == 1);
|
||||
|
||||
// Testing that exceptions can propergate through coroutines
|
||||
auto throw_in_task = [TEST_CTX]() -> Task<> {
|
||||
auto f = []() -> Task<> { throw std::runtime_error("test error"); };
|
||||
|
||||
CHECK_THROWS_AS(co_await f(), std::runtime_error);
|
||||
};
|
||||
sync_wait(throw_in_task());
|
||||
|
||||
// Test sync_wait propergrates exception
|
||||
auto throws = []() -> Task<> {
|
||||
throw std::runtime_error("bla");
|
||||
co_return;
|
||||
};
|
||||
CHECK_THROWS_AS(sync_wait(throws()), std::runtime_error);
|
||||
|
||||
// Test co_return non-copyable object works
|
||||
auto return_unique_ptr = [TEST_CTX]() -> Task<std::unique_ptr<int>> {
|
||||
co_return std::make_unique<int>(42);
|
||||
};
|
||||
CHECK(*sync_wait(return_unique_ptr()) == 42);
|
||||
|
||||
// Test co_awaiting non-copyable object works
|
||||
auto await_non_copyable = [TEST_CTX]() -> Task<> {
|
||||
auto return_unique_ptr = []() -> Task<std::unique_ptr<int>> {
|
||||
co_return std::make_unique<int>(123);
|
||||
};
|
||||
auto ptr = co_await return_unique_ptr();
|
||||
CHECK(*ptr == 123);
|
||||
};
|
||||
sync_wait(await_non_copyable());
|
||||
}
|
||||
|
||||
DROGON_TEST(CompilcatedCoroutineLifetime)
|
||||
{
|
||||
auto coro = []() -> Task<Task<std::string>> {
|
||||
auto coro2 = []() -> Task<std::string> {
|
||||
auto coro3 = []() -> Task<std::string> {
|
||||
co_return std::string("Hello, World!");
|
||||
};
|
||||
auto coro4 = [coro3 = std::move(coro3)]() -> Task<std::string> {
|
||||
auto coro5 = []() -> Task<> { co_return; };
|
||||
co_await coro5();
|
||||
co_return co_await coro3();
|
||||
};
|
||||
co_return co_await coro4();
|
||||
};
|
||||
|
||||
co_return coro2();
|
||||
};
|
||||
|
||||
auto task1 = coro();
|
||||
auto task2 = sync_wait(task1);
|
||||
std::string str = sync_wait(task2);
|
||||
|
||||
CHECK(str == "Hello, World!");
|
||||
}
|
||||
|
||||
DROGON_TEST(CoroutineDestruction)
|
||||
{
|
||||
// Test coroutine destruction
|
||||
auto destruct = []() -> Task<> {
|
||||
auto awaitStruct = []() -> Task<std::shared_ptr<internal::SomeStruct>> {
|
||||
co_return co_await internal::StructAwaiter();
|
||||
};
|
||||
|
||||
auto awaitNothing = [awaitStruct]() -> Task<> {
|
||||
co_await awaitStruct();
|
||||
};
|
||||
|
||||
co_await awaitNothing();
|
||||
};
|
||||
sync_wait(destruct());
|
||||
CHECK(internal::SomeStruct::beenDestructed == true);
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
#include <drogon/DrObject.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
class TestA : public DrObject<TestA>
|
||||
{
|
||||
};
|
||||
@ -13,36 +12,31 @@ class TestB : public DrObject<TestB>
|
||||
{
|
||||
};
|
||||
} // namespace test
|
||||
TEST(DrObjectTest, creationTest)
|
||||
|
||||
DROGON_TEST(DrObjectCreationTest)
|
||||
{
|
||||
using PtrType = std::shared_ptr<DrObjectBase>;
|
||||
auto obj = PtrType(DrClassMap::newObject("TestA"));
|
||||
EXPECT_NE(obj, nullptr);
|
||||
CHECK(obj != nullptr);
|
||||
|
||||
auto objPtr = DrClassMap::getSingleInstance("TestA");
|
||||
EXPECT_NE(objPtr.get(), nullptr);
|
||||
CHECK(objPtr.get() != nullptr);
|
||||
|
||||
auto objPtr2 = DrClassMap::getSingleInstance<TestA>();
|
||||
EXPECT_NE(objPtr2.get(), nullptr);
|
||||
EXPECT_EQ(objPtr, objPtr2);
|
||||
CHECK(objPtr2.get() != nullptr);
|
||||
CHECK(objPtr == objPtr2);
|
||||
}
|
||||
|
||||
TEST(DrObjectTest, namespaceTest)
|
||||
DROGON_TEST(DrObjectNamespaceTest)
|
||||
{
|
||||
using PtrType = std::shared_ptr<DrObjectBase>;
|
||||
auto obj = PtrType(DrClassMap::newObject("test::TestB"));
|
||||
EXPECT_NE(obj, nullptr);
|
||||
CHECK(obj != nullptr);
|
||||
|
||||
auto objPtr = DrClassMap::getSingleInstance("test::TestB");
|
||||
EXPECT_NE(objPtr.get(), nullptr);
|
||||
CHECK(objPtr.get() != nullptr);
|
||||
|
||||
auto objPtr2 = DrClassMap::getSingleInstance<test::TestB>();
|
||||
EXPECT_NE(objPtr2.get(), nullptr);
|
||||
EXPECT_EQ(objPtr, objPtr2);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
auto objPtr2 = DrClassMap::getSingleInstance<::test::TestB>();
|
||||
CHECK(objPtr2.get() != nullptr);
|
||||
CHECK(objPtr == objPtr2);
|
||||
}
|
42
lib/tests/unittests/FileTypeTest.cc
Normal file
42
lib/tests/unittests/FileTypeTest.cc
Normal file
@ -0,0 +1,42 @@
|
||||
#include "../lib/src/HttpUtils.h"
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(ExtensionTest)
|
||||
{
|
||||
SUBSECTION(normal)
|
||||
{
|
||||
std::string str{"drogon.jpg"};
|
||||
CHECK(getFileExtension(str) == "jpg");
|
||||
}
|
||||
|
||||
SUBSECTION(negative)
|
||||
{
|
||||
std::string str{"drogon."};
|
||||
CHECK(getFileExtension(str) == "");
|
||||
str = "drogon";
|
||||
CHECK(getFileExtension(str) == "");
|
||||
str = "";
|
||||
CHECK(getFileExtension(str) == "");
|
||||
str = "....";
|
||||
CHECK(getFileExtension(str) == "");
|
||||
}
|
||||
}
|
||||
|
||||
DROGON_TEST(FileTypeTest)
|
||||
{
|
||||
SUBSECTION(normal)
|
||||
{
|
||||
CHECK(parseFileType("jpg") == FT_IMAGE);
|
||||
CHECK(parseFileType("mp4") == FT_MEDIA);
|
||||
CHECK(parseFileType("csp") == FT_CUSTOM);
|
||||
CHECK(parseFileType("html") == FT_DOCUMENT);
|
||||
}
|
||||
|
||||
SUBSECTION(negative)
|
||||
{
|
||||
CHECK(parseFileType("") == FT_UNKNOWN);
|
||||
CHECK(parseFileType("don'tknow") == FT_CUSTOM);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
int main()
|
||||
DROGON_TEST(Gzip)
|
||||
{
|
||||
const std::string inStr =
|
||||
"Applications\n"
|
||||
@ -312,19 +312,8 @@ int main()
|
||||
"origin\n"
|
||||
"rawpacket";
|
||||
auto ret = utils::gzipCompress(inStr.c_str(), inStr.length());
|
||||
if (!ret.empty())
|
||||
{
|
||||
std::cout << "origin length=" << inStr.length()
|
||||
<< " compressing length=" << ret.length() << std::endl;
|
||||
REQUIRE(ret.empty() == false);
|
||||
|
||||
auto decompressStr = utils::gzipDecompress(ret.data(), ret.length());
|
||||
if (!decompressStr.empty())
|
||||
{
|
||||
std::cout << "decompressing length=" << decompressStr.length()
|
||||
<< std::endl;
|
||||
std::cout << decompressStr;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
auto decompressStr = utils::gzipDecompress(ret.data(), ret.length());
|
||||
CHECK(inStr == decompressStr);
|
||||
}
|
24
lib/tests/unittests/HttpDateTest.cc
Normal file
24
lib/tests/unittests/HttpDateTest.cc
Normal file
@ -0,0 +1,24 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(HttpDate)
|
||||
{
|
||||
// RFC 850
|
||||
auto date = utils::getHttpDate("Fri, 05-Jun-20 09:19:38 GMT");
|
||||
CHECK(date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC == 1591348778);
|
||||
|
||||
// Reddit format
|
||||
date = utils::getHttpDate("Fri, 05-Jun-2020 09:19:38 GMT");
|
||||
CHECK(date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC == 1591348778);
|
||||
|
||||
// Invalid
|
||||
date = utils::getHttpDate("Fri, this format is invalid");
|
||||
CHECK(date.microSecondsSinceEpoch() == std::numeric_limits<int64_t>::max());
|
||||
|
||||
// ASC Time
|
||||
auto epoch = time(nullptr);
|
||||
auto str = asctime(gmtime(&epoch));
|
||||
date = utils::getHttpDate(str);
|
||||
CHECK(date.microSecondsSinceEpoch() / MICRO_SECONDS_PRE_SEC == epoch);
|
||||
}
|
@ -1,11 +1,12 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
int main()
|
||||
DROGON_TEST(HttpFullDateTest)
|
||||
{
|
||||
auto str = utils::getHttpFullDate();
|
||||
std::cout << str << std::endl;
|
||||
auto date = utils::getHttpDate(str);
|
||||
std::cout << utils::getHttpFullDate(date) << std::endl;
|
||||
}
|
||||
CHECK(utils::getHttpFullDate(date) == str);
|
||||
}
|
37
lib/tests/unittests/HttpHeaderTest.cc
Normal file
37
lib/tests/unittests/HttpHeaderTest.cc
Normal file
@ -0,0 +1,37 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
#include <drogon/HttpResponse.h>
|
||||
#include "../../lib/src/HttpResponseImpl.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(HttpHeaderRequest)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->addHeader("Abc", "abc");
|
||||
CHECK(req->getHeader("Abc") == "abc");
|
||||
CHECK(req->getHeader("abc") == "abc");
|
||||
|
||||
req->removeHeader("Abc");
|
||||
CHECK(req->getHeader("abc") == "");
|
||||
}
|
||||
DROGON_TEST(HttpHeaderResponse)
|
||||
{
|
||||
auto resp = std::dynamic_pointer_cast<HttpResponseImpl>(
|
||||
HttpResponse::newHttpResponse());
|
||||
REQUIRE(resp != nullptr);
|
||||
resp->addHeader("Abc", "abc");
|
||||
CHECK(resp->getHeader("Abc") == "abc");
|
||||
CHECK(resp->getHeader("abc") == "abc");
|
||||
resp->makeHeaderString();
|
||||
|
||||
auto buffer = resp->renderToBuffer();
|
||||
auto str = std::string{buffer->peek(), buffer->readableBytes()};
|
||||
CHECK(str.find("abc") != std::string::npos);
|
||||
|
||||
resp->removeHeader("Abc");
|
||||
buffer = resp->renderToBuffer();
|
||||
str = std::string{buffer->peek(), buffer->readableBytes()};
|
||||
CHECK(str.find("abc") == std::string::npos);
|
||||
CHECK(resp->getHeader("abc") == "");
|
||||
}
|
41
lib/tests/unittests/HttpViewDataTest.cc
Normal file
41
lib/tests/unittests/HttpViewDataTest.cc
Normal file
@ -0,0 +1,41 @@
|
||||
#include <drogon/HttpViewData.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(HttpViewData)
|
||||
{
|
||||
HttpViewData data;
|
||||
data.insert("1", 1);
|
||||
data.insertAsString("2", 2.0);
|
||||
data.insertFormattedString("3", "third value is %d", 3);
|
||||
data.insertAsString("4", "4");
|
||||
data.insert("5", 5);
|
||||
data.insert("5", std::string("5!!!!!!!")); // Overides the old value
|
||||
char six = 6;
|
||||
data.insert("6", six);
|
||||
|
||||
CHECK(data.get<int>("1") == 1);
|
||||
CHECK(data.get<std::string>("2") == "2");
|
||||
CHECK(data.get<std::string>("3") == "third value is 3");
|
||||
CHECK(data.get<std::string>("4") == "4");
|
||||
CHECK(data.get<std::string>("5") == "5!!!!!!!");
|
||||
CHECK(data.get<char>("6") == 6);
|
||||
CHECK(data.get<int>("1") == 1); // get a second time
|
||||
|
||||
// Bad key returns a default constructed value
|
||||
CHECK_NOTHROW(data.get<int>("this_does_not_exist"));
|
||||
|
||||
SUBSECTION(Translate)
|
||||
{
|
||||
CHECK(HttpViewData::needTranslation("") == false);
|
||||
CHECK(HttpViewData::needTranslation("!)(*#") == false);
|
||||
CHECK(HttpViewData::needTranslation("#include <iostream>") == true);
|
||||
CHECK(HttpViewData::needTranslation("<body></body>") == true);
|
||||
|
||||
CHECK(HttpViewData::htmlTranslate("#include <iostream>") ==
|
||||
"#include <iostream>");
|
||||
CHECK(HttpViewData::htmlTranslate(">") == "&gt;");
|
||||
}
|
||||
}
|
12
lib/tests/unittests/MD5Test.cc
Normal file
12
lib/tests/unittests/MD5Test.cc
Normal file
@ -0,0 +1,12 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
|
||||
DROGON_TEST(Md5Test)
|
||||
{
|
||||
CHECK(drogon::utils::getMd5("123456789012345678901234567890123456789012345"
|
||||
"678901234567890123456789012345678901234567890"
|
||||
"1234567890") ==
|
||||
"49CB3608E2B33FAD6B65DF8CB8F49668");
|
||||
CHECK(drogon::utils::getMd5("1") == "C4CA4238A0B923820DCC509A6F75849B");
|
||||
}
|
40
lib/tests/unittests/MainLoopTest.cc
Normal file
40
lib/tests/unittests/MainLoopTest.cc
Normal file
@ -0,0 +1,40 @@
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
struct TestCookie
|
||||
{
|
||||
TestCookie(std::shared_ptr<test::CaseBase> ctx) : TEST_CTX(ctx)
|
||||
{
|
||||
}
|
||||
~TestCookie()
|
||||
{
|
||||
if (!taken)
|
||||
FAIL("Test cookie not taken");
|
||||
else
|
||||
SUCCESS();
|
||||
}
|
||||
void take()
|
||||
{
|
||||
taken = true;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool taken = false;
|
||||
std::shared_ptr<test::CaseBase> TEST_CTX;
|
||||
};
|
||||
|
||||
DROGON_TEST(MainLoopTest)
|
||||
{
|
||||
auto cookie = std::make_shared<TestCookie>(TEST_CTX);
|
||||
drogon::app().getLoop()->queueInLoop([cookie]() { cookie->take(); });
|
||||
|
||||
std::thread t([TEST_CTX]() {
|
||||
auto cookie2 = std::make_shared<TestCookie>(TEST_CTX);
|
||||
drogon::app().getLoop()->queueInLoop([cookie2]() { cookie2->take(); });
|
||||
});
|
||||
t.join();
|
||||
}
|
65
lib/tests/unittests/MsgBufferTest.cc
Normal file
65
lib/tests/unittests/MsgBufferTest.cc
Normal file
@ -0,0 +1,65 @@
|
||||
#include <trantor/utils/MsgBuffer.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace trantor;
|
||||
DROGON_TEST(MsgBufferTest)
|
||||
{
|
||||
SUBSECTION(readableTest)
|
||||
{
|
||||
MsgBuffer buffer;
|
||||
|
||||
CHECK(buffer.readableBytes() == 0);
|
||||
buffer.append(std::string(128, 'a'));
|
||||
CHECK(buffer.readableBytes() == 128);
|
||||
buffer.retrieve(100);
|
||||
CHECK(buffer.readableBytes() == 28);
|
||||
CHECK(buffer.peekInt8() == 'a');
|
||||
buffer.retrieveAll();
|
||||
CHECK(buffer.readableBytes() == 0);
|
||||
}
|
||||
|
||||
SUBSECTION(writableTest)
|
||||
{
|
||||
MsgBuffer buffer(100);
|
||||
|
||||
CHECK(buffer.writableBytes() == 100);
|
||||
buffer.append("abcde");
|
||||
CHECK(buffer.writableBytes() == 95);
|
||||
buffer.append(std::string(100, 'x'));
|
||||
CHECK(buffer.writableBytes() == 111);
|
||||
buffer.retrieve(100);
|
||||
CHECK(buffer.writableBytes() == 111);
|
||||
buffer.append(std::string(112, 'c'));
|
||||
CHECK(buffer.writableBytes() == 99);
|
||||
buffer.retrieveAll();
|
||||
CHECK(buffer.writableBytes() == 216);
|
||||
}
|
||||
|
||||
SUBSECTION(addInFrontTest)
|
||||
{
|
||||
MsgBuffer buffer(100);
|
||||
|
||||
CHECK(buffer.writableBytes() == 100);
|
||||
buffer.addInFrontInt8('a');
|
||||
CHECK(buffer.writableBytes() == 100);
|
||||
buffer.addInFrontInt64(123);
|
||||
CHECK(buffer.writableBytes() == 92);
|
||||
buffer.addInFrontInt64(100);
|
||||
CHECK(buffer.writableBytes() == 84);
|
||||
buffer.addInFrontInt8(1);
|
||||
CHECK(buffer.writableBytes() == 84);
|
||||
}
|
||||
|
||||
SUBSECTION(MoveAssignmentOperator)
|
||||
{
|
||||
MsgBuffer buf(100);
|
||||
const char *bufptr = buf.peek();
|
||||
size_t writable = buf.writableBytes();
|
||||
MsgBuffer buffnew(1000);
|
||||
buffnew = std::move(buf);
|
||||
CHECK(bufptr == buffnew.peek());
|
||||
CHECK(writable == buffnew.writableBytes());
|
||||
}
|
||||
}
|
59
lib/tests/unittests/OStringStreamTest.cc
Normal file
59
lib/tests/unittests/OStringStreamTest.cc
Normal file
@ -0,0 +1,59 @@
|
||||
#include <drogon/utils/OStringStream.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
DROGON_TEST(OStringStreamTest)
|
||||
{
|
||||
SUBSECTION(interger)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << 12;
|
||||
ss << 345L;
|
||||
CHECK(ss.str() == "12345");
|
||||
}
|
||||
|
||||
SUBSECTION(float_number)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << 3.14f;
|
||||
ss << 3.1416;
|
||||
CHECK(ss.str() == "3.143.1416");
|
||||
}
|
||||
|
||||
SUBSECTION(literal_string)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << "hello";
|
||||
ss << " world";
|
||||
CHECK(ss.str() == "hello world");
|
||||
}
|
||||
|
||||
SUBSECTION(string_view)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << drogon::string_view("hello");
|
||||
ss << drogon::string_view(" world");
|
||||
CHECK(ss.str() == "hello world");
|
||||
}
|
||||
|
||||
SUBSECTION(std_string)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << std::string("hello");
|
||||
ss << std::string(" world");
|
||||
CHECK(ss.str() == "hello world");
|
||||
}
|
||||
|
||||
SUBSECTION(mix)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << std::string("hello");
|
||||
ss << drogon::string_view(" world");
|
||||
ss << "!";
|
||||
ss << 123;
|
||||
ss << 3.14f;
|
||||
|
||||
CHECK(ss.str() == "hello world!1233.14");
|
||||
}
|
||||
}
|
18
lib/tests/unittests/PubSubServiceUnittest.cc
Normal file
18
lib/tests/unittests/PubSubServiceUnittest.cc
Normal file
@ -0,0 +1,18 @@
|
||||
#include <drogon/PubSubService.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
DROGON_TEST(PubSubServiceTest)
|
||||
{
|
||||
drogon::PubSubService<std::string> service;
|
||||
auto id = service.subscribe("topic1",
|
||||
[TEST_CTX](const std::string &topic,
|
||||
const std::string &message) {
|
||||
CHECK(topic == "topic1");
|
||||
CHECK(message == "hello world");
|
||||
});
|
||||
service.publish("topic1", "hello world");
|
||||
service.publish("topic2", "hello world");
|
||||
CHECK(service.size() == 1);
|
||||
service.unsubscribe("topic1", id);
|
||||
CHECK(service.size() == 0);
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
#include "../lib/src/ssl_funcs/Sha1.h"
|
||||
#include <gtest/gtest.h>
|
||||
#include "../../lib/src/ssl_funcs/Sha1.h"
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
|
||||
TEST(SHA1Test, sha1)
|
||||
DROGON_TEST(SHA1Test)
|
||||
{
|
||||
unsigned char in[] =
|
||||
"1234567890123456789012345678901234567890123456789012345"
|
||||
@ -13,11 +13,5 @@ TEST(SHA1Test, sha1)
|
||||
outStr.resize(SHA_DIGEST_LENGTH * 2);
|
||||
for (int i = 0; i < SHA_DIGEST_LENGTH; ++i)
|
||||
sprintf((char *)(outStr.data() + i * 2), "%02x", out[i]);
|
||||
EXPECT_EQ(outStr, "fecfd28bbc9345891a66d7c1b8ff46e60192d284");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
CHECK(outStr == "fecfd28bbc9345891a66d7c1b8ff46e60192d284");
|
||||
}
|
91
lib/tests/unittests/StringOpsTest.cc
Normal file
91
lib/tests/unittests/StringOpsTest.cc
Normal file
@ -0,0 +1,91 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
|
||||
struct SameContent
|
||||
{
|
||||
SameContent(const std::vector<std::string>& container)
|
||||
: container_(container.begin(), container.end())
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<std::string> container_;
|
||||
};
|
||||
|
||||
template <typename Container1>
|
||||
inline bool operator==(const Container1& a, const SameContent& wrapper)
|
||||
{
|
||||
const auto& b = wrapper.container_;
|
||||
if (a.size() != b.size())
|
||||
return false;
|
||||
|
||||
auto ait = a.begin();
|
||||
auto bit = b.begin();
|
||||
|
||||
while (ait != a.end() && bit != b.end())
|
||||
{
|
||||
if (*ait != *bit)
|
||||
break;
|
||||
ait++;
|
||||
bit++;
|
||||
}
|
||||
return ait == a.end() && bit == b.end();
|
||||
}
|
||||
|
||||
using namespace drogon;
|
||||
DROGON_TEST(StringOpsTest)
|
||||
{
|
||||
SUBSECTION(SplitString)
|
||||
{
|
||||
std::string str = "1,2,3,3,,4";
|
||||
CHECK(utils::splitString(str, ",") ==
|
||||
SameContent({"1", "2", "3", "3", "4"}));
|
||||
CHECK(utils::splitString(str, ",", true) ==
|
||||
SameContent({"1", "2", "3", "3", "", "4"}));
|
||||
CHECK(utils::splitString(str, "|", true) ==
|
||||
SameContent({"1,2,3,3,,4"}));
|
||||
|
||||
str = "a||b||c||||";
|
||||
CHECK(utils::splitString(str, "||") == SameContent({"a", "b", "c"}));
|
||||
CHECK(utils::splitString(str, "||", true) ==
|
||||
SameContent({"a", "b", "c", "", ""}));
|
||||
|
||||
str = "aabbbaabbbb";
|
||||
CHECK(utils::splitString(str, "bb") == SameContent({"aa", "baa"}));
|
||||
CHECK(utils::splitString(str, "bb", true) ==
|
||||
SameContent({"aa", "baa", "", ""}));
|
||||
|
||||
str = "";
|
||||
CHECK(utils::splitString(str, ",") == SameContent({}));
|
||||
CHECK(utils::splitString(str, ",", true) == SameContent({""}));
|
||||
}
|
||||
|
||||
SUBSECTION(SplitStringToSet)
|
||||
{
|
||||
// splitStringToSet ignores empty strings
|
||||
std::string str = "1,2,3,3,,4";
|
||||
auto s = utils::splitStringToSet(str, ",");
|
||||
CHECK(s.size() == 4);
|
||||
CHECK(s.count("1") == 1);
|
||||
CHECK(s.count("2") == 1);
|
||||
CHECK(s.count("3") == 1);
|
||||
CHECK(s.count("4") == 1);
|
||||
|
||||
str = "a|||a";
|
||||
s = utils::splitStringToSet(str, "||");
|
||||
CHECK(s.size() == 2);
|
||||
CHECK(s.count("a") == 1);
|
||||
CHECK(s.count("|a") == 1);
|
||||
}
|
||||
|
||||
SUBSECTION(ReplaceAll)
|
||||
{
|
||||
std::string str = "3.14159";
|
||||
utils::replaceAll(str, "1", "a");
|
||||
CHECK(str == "3.a4a59");
|
||||
|
||||
str = "aaxxxaaxxxxaaxxxxx";
|
||||
utils::replaceAll(str, "xx", "oo");
|
||||
CHECK(str == "aaooxaaooooaaoooox");
|
||||
}
|
||||
}
|
14
lib/tests/unittests/UrlCodecTest.cc
Normal file
14
lib/tests/unittests/UrlCodecTest.cc
Normal file
@ -0,0 +1,14 @@
|
||||
#include <drogon/utils/string_view.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
DROGON_TEST(URLCodec)
|
||||
{
|
||||
std::string input = "k1=1&k2=安";
|
||||
auto encoded = drogon::utils::urlEncode(input);
|
||||
auto decoded = drogon::utils::urlDecode(encoded);
|
||||
|
||||
CHECK(encoded == "k1=1&k2=%E5%AE%89");
|
||||
CHECK(input == decoded);
|
||||
}
|
52
lib/tests/unittests/main.cc
Normal file
52
lib/tests/unittests/main.cc
Normal file
@ -0,0 +1,52 @@
|
||||
#define DROGON_TEST_MAIN
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace trantor;
|
||||
|
||||
DROGON_TEST(TestFrameworkSelfTest)
|
||||
{
|
||||
CHECK(TEST_CTX->name() == "TestFrameworkSelfTest");
|
||||
CHECK(true);
|
||||
CHECK(false != true);
|
||||
CHECK(1 * 2 == 1 + 1);
|
||||
CHECK(42 < 100);
|
||||
CHECK(0xff <= 255);
|
||||
CHECK('a' >= 'a');
|
||||
CHECK(3.14159 > 2.71828);
|
||||
CHECK(nullptr == nullptr);
|
||||
CHECK_THROWS(throw std::runtime_error("test exception"));
|
||||
CHECK_THROWS_AS(throw std::domain_error("test exception"),
|
||||
std::domain_error);
|
||||
CHECK_NOTHROW([] { return 0; }());
|
||||
STATIC_REQUIRE(std::is_standard_layout<int>::value);
|
||||
STATIC_REQUIRE(std::is_default_constructible<test::Case>::value == false);
|
||||
|
||||
auto child_test = std::make_shared<test::Case>(TEST_CTX, "ChildTest");
|
||||
CHECK(child_test->fullname() == "TestFrameworkSelfTest.ChildTest");
|
||||
|
||||
// Unlike Catch2, a subsection in drogon test does not provide a fixture
|
||||
// It's only a way to signify testing different for things
|
||||
SUBSECTION(Subsection)
|
||||
{
|
||||
CHECK(TEST_CTX->fullname() == "TestFrameworkSelfTest.Subsection");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
std::promise<void> p1;
|
||||
std::future<void> f1 = p1.get_future();
|
||||
|
||||
std::thread thr([&]() {
|
||||
p1.set_value();
|
||||
app().run();
|
||||
});
|
||||
|
||||
f1.get();
|
||||
int testStatus = test::run(argc, argv);
|
||||
app().getLoop()->queueInLoop([]() { app().quit(); });
|
||||
thr.join();
|
||||
return testStatus;
|
||||
}
|
@ -1,137 +1,104 @@
|
||||
#define DROGON_TEST_MAIN
|
||||
#include <drogon/nosql/RedisClient.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/drogon.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#define RESET "\033[0m"
|
||||
#define RED "\033[31m" /* Red */
|
||||
#define GREEN "\033[32m" /* Green */
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
using namespace drogon::nosql;
|
||||
#ifdef __cpp_impl_coroutine
|
||||
#define TEST_COUNT 8
|
||||
#else
|
||||
#define TEST_COUNT 7
|
||||
#endif
|
||||
std::promise<int> pro;
|
||||
auto globalf = pro.get_future();
|
||||
int counter = 0;
|
||||
void addCount(int &count, std::promise<int> &pro)
|
||||
{
|
||||
++count;
|
||||
// LOG_DEBUG << count;
|
||||
if (count == TEST_COUNT)
|
||||
{
|
||||
pro.set_value(1);
|
||||
}
|
||||
}
|
||||
void testoutput(bool isGood, const std::string &testMessage)
|
||||
{
|
||||
if (isGood)
|
||||
{
|
||||
std::cout << GREEN << counter + 1 << ".\t" << testMessage << "\t\tOK\n";
|
||||
std::cout << RESET;
|
||||
addCount(counter, pro);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << RED << testMessage << "\t\tBAD\n";
|
||||
std::cout << RESET;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
void doTest(const RedisClientPtr &redisClient)
|
||||
RedisClientPtr redisClient;
|
||||
DROGON_TEST(RedisTest)
|
||||
{
|
||||
redisClient = drogon::nosql::RedisClient::newRedisClient(
|
||||
trantor::InetAddress("127.0.0.1", 6379), 1);
|
||||
REQUIRE(redisClient != nullptr);
|
||||
// std::this_thread::sleep_for(1s);
|
||||
redisClient->newTransactionAsync([](const RedisTransactionPtr &transPtr) {
|
||||
// 1
|
||||
transPtr->execCommandAsync(
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(true, r.getStringForDisplaying());
|
||||
},
|
||||
[](const std::exception &err) { testoutput(false, err.what()); },
|
||||
"ping");
|
||||
// 2
|
||||
transPtr->execute(
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(true, r.getStringForDisplaying());
|
||||
},
|
||||
[](const std::exception &err) { testoutput(false, err.what()); });
|
||||
});
|
||||
redisClient->newTransactionAsync(
|
||||
[TEST_CTX](const RedisTransactionPtr &transPtr) {
|
||||
// 1
|
||||
transPtr->execCommandAsync(
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) { SUCCESS(); },
|
||||
[TEST_CTX](const std::exception &err) { MANDATE(err.what()); },
|
||||
"ping");
|
||||
// 2
|
||||
transPtr->execute(
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) { SUCCESS(); },
|
||||
[TEST_CTX](const std::exception &err) { MANDATE(err.what()); });
|
||||
});
|
||||
// 3
|
||||
redisClient->execCommandAsync(
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(true, r.getStringForDisplaying());
|
||||
},
|
||||
[](const std::exception &err) { testoutput(false, err.what()); },
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) { SUCCESS(); },
|
||||
[TEST_CTX](const std::exception &err) { MANDATE(err.what()); },
|
||||
"set %s %s",
|
||||
"id_123",
|
||||
"drogon");
|
||||
// 4
|
||||
redisClient->execCommandAsync(
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(r.type() == RedisResultType::kArray &&
|
||||
r.asArray().size() == 1,
|
||||
r.getStringForDisplaying());
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) {
|
||||
MANDATE(r.type() == RedisResultType::kArray);
|
||||
MANDATE(r.asArray().size() == 1);
|
||||
},
|
||||
[](const std::exception &err) { testoutput(false, err.what()); },
|
||||
[TEST_CTX](const std::exception &err) { MANDATE(err.what()); },
|
||||
"keys *");
|
||||
// 5
|
||||
redisClient->execCommandAsync(
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(r.asString() == "hello", r.getStringForDisplaying());
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) {
|
||||
MANDATE(r.asString() == "hello");
|
||||
},
|
||||
[](const RedisException &err) { testoutput(false, err.what()); },
|
||||
[TEST_CTX](const RedisException &err) { MANDATE(err.what()); },
|
||||
"echo %s",
|
||||
"hello");
|
||||
// 6
|
||||
redisClient->execCommandAsync(
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(true, r.getStringForDisplaying());
|
||||
},
|
||||
[](const RedisException &err) { testoutput(false, err.what()); },
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) { SUCCESS(); },
|
||||
[TEST_CTX](const RedisException &err) { MANDATE(err.what()); },
|
||||
"flushall");
|
||||
// 7
|
||||
redisClient->execCommandAsync(
|
||||
|
||||
[](const drogon::nosql::RedisResult &r) {
|
||||
testoutput(r.type() == RedisResultType::kNil,
|
||||
r.getStringForDisplaying());
|
||||
[TEST_CTX](const drogon::nosql::RedisResult &r) {
|
||||
MANDATE(r.type() == RedisResultType::kNil);
|
||||
},
|
||||
[](const RedisException &err) { testoutput(false, err.what()); },
|
||||
[TEST_CTX](const RedisException &err) { MANDATE(err.what()); },
|
||||
"get %s",
|
||||
"xxxxx");
|
||||
|
||||
std::cout << "start\n";
|
||||
#ifdef __cpp_impl_coroutine
|
||||
auto coro_test = [redisClient]() -> drogon::Task<> {
|
||||
auto coro_test = [TEST_CTX]() -> drogon::Task<> {
|
||||
// 8
|
||||
try
|
||||
{
|
||||
auto r = co_await redisClient->execCommandCoro("get %s", "haha");
|
||||
testoutput(r.type() == RedisResultType::kNil,
|
||||
r.getStringForDisplaying());
|
||||
MANDATE(r.type() == RedisResultType::kNil);
|
||||
}
|
||||
catch (const RedisException &err)
|
||||
{
|
||||
testoutput(false, err.what());
|
||||
FAULT(err.what());
|
||||
}
|
||||
};
|
||||
drogon::sync_wait(coro_test());
|
||||
#endif
|
||||
globalf.get();
|
||||
}
|
||||
|
||||
int main()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
#ifndef USE_REDIS
|
||||
LOG_DEBUG << "Drogon is built without Redis. No tests executed.";
|
||||
return 0;
|
||||
#endif
|
||||
drogon::app().setLogLevel(trantor::Logger::kWarn);
|
||||
auto redisClient = drogon::nosql::RedisClient::newRedisClient(
|
||||
trantor::InetAddress("127.0.0.1", 6379), 1);
|
||||
doTest(redisClient);
|
||||
std::cout << "Test passed\n";
|
||||
return 0;
|
||||
std::promise<void> p1;
|
||||
std::future<void> f1 = p1.get_future();
|
||||
|
||||
std::thread thr([&]() {
|
||||
p1.set_value();
|
||||
drogon::app().run();
|
||||
});
|
||||
|
||||
f1.get();
|
||||
int testStatus = drogon::test::run(argc, argv);
|
||||
drogon::app().getLoop()->queueInLoop([]() { drogon::app().quit(); });
|
||||
thr.join();
|
||||
return testStatus;
|
||||
}
|
@ -9,4 +9,4 @@ add_executable(db_test
|
||||
|
||||
set_property(TARGET db_test PROPERTY CXX_STANDARD ${DROGON_CXX_STANDARD})
|
||||
set_property(TARGET db_test PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
set_property(TARGET db_test PROPERTY CXX_EXTENSIONS OFF)
|
||||
set_property(TARGET db_test PROPERTY CXX_EXTENSIONS OFF)
|
File diff suppressed because it is too large
Load Diff
195
test
Executable file
195
test
Executable file
@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "First arg:"
|
||||
echo $1
|
||||
|
||||
os='linux'
|
||||
if [ "$1" = "-w" ]; then
|
||||
os='windows'
|
||||
fi
|
||||
|
||||
src_dir=$(pwd)
|
||||
|
||||
echo "OS:" $os
|
||||
|
||||
if [ $os = "linux" ]; then
|
||||
drogon_ctl_exec=$(pwd)/build/drogon_ctl/drogon_ctl
|
||||
else
|
||||
drogon_ctl_exec=$(pwd)/build/drogon_ctl/Debug/drogon_ctl.exe
|
||||
export PATH=$PATH:$src_dir/install/bin
|
||||
fi
|
||||
echo ${drogon_ctl_exec}
|
||||
cd build/lib/tests/
|
||||
|
||||
if [ $os = "windows" ]; then
|
||||
cd Debug
|
||||
fi
|
||||
|
||||
make_flags=''
|
||||
cmake_gen=''
|
||||
parallel=1
|
||||
|
||||
# simulate ninja's parallelism
|
||||
case $(nproc) in
|
||||
1)
|
||||
parallel=$(($(nproc) + 1))
|
||||
;;
|
||||
2)
|
||||
parallel=$(($(nproc) + 1))
|
||||
;;
|
||||
*)
|
||||
parallel=$(($(nproc) + 2))
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $os = "linux" ]; then
|
||||
if [ -f /bin/ninja ]; then
|
||||
cmake_gen='-G Ninja'
|
||||
else
|
||||
make_flags="$make_flags -j$parallel"
|
||||
fi
|
||||
fi
|
||||
|
||||
#Make integration_test_server run as a daemon
|
||||
if [ $os = "linux" ]; then
|
||||
sed -i -e "s/\"run_as_daemon.*$/\"run_as_daemon\": true\,/" config.example.json
|
||||
fi
|
||||
sed -i -e "s/\"relaunch_on_error.*$/\"relaunch_on_error\": true\,/" config.example.json
|
||||
sed -i -e "s/\"threads_num.*$/\"threads_num\": 0\,/" config.example.json
|
||||
sed -i -e "s/\"use_brotli.*$/\"use_brotli\": true\,/" config.example.json
|
||||
|
||||
if [ ! -f "integration_test_client" ]; then
|
||||
echo "Build failed"
|
||||
exit -1
|
||||
fi
|
||||
if [ ! -f "integration_test_server" ]; then
|
||||
echo "Build failed"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
killall -9 integration_test_server
|
||||
./integration_test_server &
|
||||
|
||||
sleep 4
|
||||
|
||||
echo "Running the integration test"
|
||||
./integration_test_client -s
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Integration test failed"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
killall -9 integration_test_server
|
||||
|
||||
#Test drogon_ctl
|
||||
echo "Testing drogon_ctl"
|
||||
rm -rf drogon_test
|
||||
|
||||
${drogon_ctl_exec} create project drogon_test
|
||||
|
||||
ls -la
|
||||
cd drogon_test/controllers
|
||||
|
||||
${drogon_ctl_exec} create controller Test::SimpleCtrl
|
||||
${drogon_ctl_exec} create controller -h Test::HttpCtrl
|
||||
${drogon_ctl_exec} create controller -w Test::WebsockCtrl
|
||||
${drogon_ctl_exec} create controller SimpleCtrl
|
||||
${drogon_ctl_exec} create controller -h HttpCtrl
|
||||
${drogon_ctl_exec} create controller -w WebsockCtrl
|
||||
|
||||
if [ ! -f "Test_SimpleCtrl.h" -o ! -f "Test_SimpleCtrl.cc" -o ! -f "Test_HttpCtrl.h" -o ! -f "Test_HttpCtrl.cc" -o ! -f "Test_WebsockCtrl.h" -o ! -f "Test_WebsockCtrl.cc" ]; then
|
||||
echo "Failed to create controllers"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
if [ ! -f "SimpleCtrl.h" -o ! -f "SimpleCtrl.cc" -o ! -f "HttpCtrl.h" -o ! -f "HttpCtrl.cc" -o ! -f "WebsockCtrl.h" -o ! -f "WebsockCtrl.cc" ]; then
|
||||
echo "Failed to create controllers"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
cd ../filters
|
||||
|
||||
${drogon_ctl_exec} create filter Test::TestFilter
|
||||
|
||||
if [ ! -f "Test_TestFilter.h" -o ! -f "Test_TestFilter.cc" ]; then
|
||||
echo "Failed to create filters"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
cd ../plugins
|
||||
|
||||
${drogon_ctl_exec} create plugin Test::TestPlugin
|
||||
|
||||
if [ ! -f "Test_TestPlugin.h" -o ! -f "Test_TestPlugin.cc" ]; then
|
||||
echo "Failed to create plugins"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
cd ../views
|
||||
|
||||
echo "Hello, world!" >>hello.csp
|
||||
|
||||
cd ../build
|
||||
if [ $os = "windows" ]; then
|
||||
conan install $src_dir -s compiler="Visual Studio" -s compiler.version=16 -sbuild_type=Debug -g cmake_paths
|
||||
cmake_gen="$cmake_gen -DCMAKE_TOOLCHAIN_FILE=conan_paths.cmake -DCMAKE_INSTALL_PREFIX=$src_dir/install"
|
||||
fi
|
||||
cmake .. $cmake_gen
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to run CMake for example project"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
cmake --build . -- $make_flags
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
if [ $os = "linux" ]; then
|
||||
if [ ! -f "drogon_test" ]; then
|
||||
echo "Failed to build drogon_test"
|
||||
exit -1
|
||||
fi
|
||||
else
|
||||
if [ ! -f "Debug\drogon_test.exe" ]; then
|
||||
echo "Failed to build drogon_test"
|
||||
exit -1
|
||||
fi
|
||||
fi
|
||||
|
||||
cd ../../
|
||||
rm -rf drogon_test
|
||||
|
||||
if [ "$1" = "-t" ]; then
|
||||
#unit testing
|
||||
cd ../../
|
||||
echo "Unit testing"
|
||||
cmake --build . --target test -- $make_flags
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in unit testing"
|
||||
exit -1
|
||||
fi
|
||||
if [ -f "./orm_lib/tests/db_test" ]; then
|
||||
echo "Test database"
|
||||
./orm_lib/tests/db_test -s
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing"
|
||||
exit -1
|
||||
fi
|
||||
fi
|
||||
if [ -f "./nosql_lib/redis/tests/redis_test" ]; then
|
||||
echo "Test redis"
|
||||
./nosql_lib/redis/tests/redis_test -s
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing"
|
||||
exit -1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Everything is ok!"
|
||||
exit 0
|
57
test.sh
57
test.sh
@ -19,7 +19,7 @@ else
|
||||
export PATH=$PATH:$src_dir/install/bin
|
||||
fi
|
||||
echo ${drogon_ctl_exec}
|
||||
cd build/examples/
|
||||
cd build/lib/tests/
|
||||
|
||||
if [ $os = "windows" ]; then
|
||||
cd Debug
|
||||
@ -50,7 +50,7 @@ if [ $os = "linux" ]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
#Make webapp run as a daemon
|
||||
#Make integration_test_server run as a daemon
|
||||
if [ $os = "linux" ]; then
|
||||
sed -i -e "s/\"run_as_daemon.*$/\"run_as_daemon\": true\,/" config.example.json
|
||||
fi
|
||||
@ -58,59 +58,32 @@ sed -i -e "s/\"relaunch_on_error.*$/\"relaunch_on_error\": true\,/" config.examp
|
||||
sed -i -e "s/\"threads_num.*$/\"threads_num\": 0\,/" config.example.json
|
||||
sed -i -e "s/\"use_brotli.*$/\"use_brotli\": true\,/" config.example.json
|
||||
|
||||
if [ ! -f "webapp" ]; then
|
||||
if [ ! -f "integration_test_client" ]; then
|
||||
echo "Build failed"
|
||||
exit -1
|
||||
fi
|
||||
if [ ! -f "webapp_test" ]; then
|
||||
if [ ! -f "integration_test_server" ]; then
|
||||
echo "Build failed"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
killall -9 webapp
|
||||
./webapp &
|
||||
webapppid=$!
|
||||
killall -9 integration_test_server
|
||||
./integration_test_server &
|
||||
|
||||
sleep 4
|
||||
|
||||
echo "Test http requests and responses."
|
||||
./webapp_test
|
||||
echo "Running the integration test"
|
||||
./integration_test_client -s
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing http requests"
|
||||
echo "Integration test failed"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
#Test WebSocket
|
||||
echo "Test the WebSocket"
|
||||
./websocket_test -t
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing WebSocket"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
# Test websocket client coroutine
|
||||
if [ -f ./websocket_coro_test ]; then
|
||||
echo "Test WebSocket w/ coroutine"
|
||||
./websocket_coro_test -t
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing WebSocket with coroutine"
|
||||
exit -1
|
||||
fi
|
||||
fi
|
||||
|
||||
#Test pipelining
|
||||
echo "Test the pipelining"
|
||||
./pipelining_test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing pipelining"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
kill -9 $webapppid
|
||||
killall -9 integration_test_server
|
||||
|
||||
#Test drogon_ctl
|
||||
echo "Test the drogon_ctl"
|
||||
echo "Testing drogon_ctl"
|
||||
rm -rf drogon_test
|
||||
|
||||
${drogon_ctl_exec} create project drogon_test
|
||||
@ -165,7 +138,7 @@ fi
|
||||
cmake .. $cmake_gen
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing"
|
||||
echo "Failed to run CMake for example project"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
@ -193,7 +166,7 @@ rm -rf drogon_test
|
||||
|
||||
if [ "$1" = "-t" ]; then
|
||||
#unit testing
|
||||
cd ../
|
||||
cd ../../
|
||||
echo "Unit testing"
|
||||
cmake --build . --target test -- $make_flags
|
||||
if [ $? -ne 0 ]; then
|
||||
@ -202,7 +175,7 @@ if [ "$1" = "-t" ]; then
|
||||
fi
|
||||
if [ -f "./orm_lib/tests/db_test" ]; then
|
||||
echo "Test database"
|
||||
./orm_lib/tests/db_test
|
||||
./orm_lib/tests/db_test -s
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing"
|
||||
exit -1
|
||||
@ -210,7 +183,7 @@ if [ "$1" = "-t" ]; then
|
||||
fi
|
||||
if [ -f "./nosql_lib/redis/tests/redis_test" ]; then
|
||||
echo "Test redis"
|
||||
./nosql_lib/redis/tests/redis_test
|
||||
./nosql_lib/redis/tests/redis_test -s
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Error in testing"
|
||||
exit -1
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user