复现已有算法
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
#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(in);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
|
||||
SUBSECTION(InvalidChars)
|
||||
{
|
||||
auto decoded =
|
||||
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw*=*=");
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
|
||||
SUBSECTION(InvalidCharsNoPadding)
|
||||
{
|
||||
auto decoded =
|
||||
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw**");
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
|
||||
SUBSECTION(Unpadded)
|
||||
{
|
||||
std::string in{"drogon framework"};
|
||||
auto encoded = drogon::utils::base64EncodeUnpadded(in);
|
||||
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(in);
|
||||
auto out2 = drogon::utils::base64Decode(out);
|
||||
auto encoded = drogon::utils::base64Encode(in);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
|
||||
SUBSECTION(URLSafe)
|
||||
{
|
||||
std::string in{"drogon framework"};
|
||||
auto encoded = drogon::utils::base64Encode(in, true);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
|
||||
SUBSECTION(UnpaddedURLSafe)
|
||||
{
|
||||
std::string in{"drogon framework"};
|
||||
auto encoded = drogon::utils::base64EncodeUnpadded(in, 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(in, true);
|
||||
auto decoded = drogon::utils::base64Decode(encoded);
|
||||
CHECK(decoded == in);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#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.1f,
|
||||
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 == "-");
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#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");
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#include <drogon/HttpController.h>
|
||||
#include <drogon/HttpSimpleController.h>
|
||||
#include <drogon/WebSocketController.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
class Ctrl : public drogon::HttpController<Ctrl, false>
|
||||
{
|
||||
public:
|
||||
static void initPathRouting()
|
||||
{
|
||||
created = true;
|
||||
};
|
||||
|
||||
static bool created;
|
||||
};
|
||||
|
||||
class SimpleCtrl : public drogon::HttpController<Ctrl, false>
|
||||
{
|
||||
public:
|
||||
static void initPathRouting()
|
||||
{
|
||||
created = true;
|
||||
};
|
||||
|
||||
static bool created;
|
||||
};
|
||||
|
||||
class WsCtrl : public drogon::WebSocketController<WsCtrl, false>
|
||||
{
|
||||
public:
|
||||
static void initPathRouting()
|
||||
{
|
||||
created = true;
|
||||
};
|
||||
|
||||
static bool created;
|
||||
};
|
||||
|
||||
bool Ctrl::created = false;
|
||||
bool SimpleCtrl::created = false;
|
||||
bool WsCtrl::created = false;
|
||||
|
||||
DROGON_TEST(ControllerCreation)
|
||||
{
|
||||
REQUIRE(Ctrl::created == false);
|
||||
REQUIRE(SimpleCtrl::created == false);
|
||||
REQUIRE(WsCtrl::created == false);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#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");
|
||||
|
||||
drogon::Cookie cookie4("test", "4");
|
||||
cookie4.setMaxAge(3600);
|
||||
cookie4.setSameSite(drogon::Cookie::SameSite::kStrict);
|
||||
CHECK(cookie4.cookieString() ==
|
||||
"Set-Cookie: test=4; Max-Age=3600; "
|
||||
"SameSite=Strict; HttpOnly\r\n");
|
||||
drogon::Cookie cookie5("test", "5");
|
||||
cookie5.setSameSite(drogon::Cookie::SameSite::kNone);
|
||||
CHECK(cookie5.cookieString() ==
|
||||
"Set-Cookie: test=5; "
|
||||
"SameSite=None; Secure; HttpOnly\r\n");
|
||||
|
||||
CHECK(drogon::Cookie::SameSite::kLax ==
|
||||
drogon::Cookie::convertString2SameSite("Lax"));
|
||||
CHECK(drogon::Cookie::SameSite::kStrict ==
|
||||
drogon::Cookie::convertString2SameSite("Strict"));
|
||||
CHECK(drogon::Cookie::SameSite::kNone ==
|
||||
drogon::Cookie::convertString2SameSite("None"));
|
||||
|
||||
// Test for Partitioned attribute
|
||||
drogon::Cookie cookie6("test", "6");
|
||||
cookie6.setPartitioned(true);
|
||||
CHECK(cookie6.cookieString() ==
|
||||
"Set-Cookie: test=6; Secure; HttpOnly; Partitioned\r\n");
|
||||
// Test that partitioned attribute automatically sets secure
|
||||
drogon::Cookie cookie7("test", "7");
|
||||
cookie7.setPartitioned(true);
|
||||
CHECK(cookie7.isSecure() == true);
|
||||
// Test other attributes
|
||||
drogon::Cookie cookie8("test", "8");
|
||||
cookie8.setPartitioned(true);
|
||||
cookie8.setDomain("drogon.org");
|
||||
cookie8.setMaxAge(3600);
|
||||
CHECK(cookie8.cookieString() ==
|
||||
"Set-Cookie: test=8; Max-Age=3600; Domain=drogon.org; Secure; "
|
||||
"HttpOnly; Partitioned\r\n");
|
||||
// Teset Partitioned and SameSite can coexist
|
||||
drogon::Cookie cookie9("test", "9");
|
||||
cookie9.setPartitioned(true);
|
||||
cookie9.setSameSite(drogon::Cookie::SameSite::kLax);
|
||||
CHECK(
|
||||
cookie9.cookieString() ==
|
||||
"Set-Cookie: test=9; SameSite=Lax; Secure; HttpOnly; Partitioned\r\n");
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/utils/coroutine.h>
|
||||
#include <drogon/HttpAppFramework.h>
|
||||
#include <trantor/net/EventLoopThread.h>
|
||||
#include <trantor/net/EventLoopThreadPool.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
|
||||
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
|
||||
|
||||
// Workaround 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 propagate 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 propagates 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());
|
||||
|
||||
// This only works because async_run tries to run the coroutine as soon as
|
||||
// possible and the coroutine does not wait
|
||||
int testVar = 0;
|
||||
async_run([&testVar]() -> Task<void> {
|
||||
testVar = 1;
|
||||
co_return;
|
||||
});
|
||||
CHECK(testVar == 1);
|
||||
async_run([TEST_CTX]() -> Task<void> {
|
||||
auto val =
|
||||
co_await queueInLoopCoro<int>(app().getLoop(), []() { return 42; });
|
||||
CHECK(val == 42);
|
||||
});
|
||||
async_run([TEST_CTX]() -> Task<void> {
|
||||
co_await queueInLoopCoro<void>(app().getLoop(), []() { LOG_DEBUG; });
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
DROGON_TEST(AsyncWaitLifetime)
|
||||
{
|
||||
app().getLoop()->queueInLoop([TEST_CTX]() {
|
||||
async_run([TEST_CTX]() -> Task<> {
|
||||
auto ptr = std::make_shared<std::string>("test");
|
||||
CHECK(ptr.use_count() == 1);
|
||||
co_await sleepCoro(drogon::app().getLoop(), 0.01);
|
||||
CHECK(ptr.use_count() == 1);
|
||||
});
|
||||
});
|
||||
|
||||
app().getLoop()->queueInLoop([TEST_CTX]() {
|
||||
auto ptr = std::make_shared<std::string>("test");
|
||||
async_run([ptr, TEST_CTX]() -> Task<> {
|
||||
CHECK(ptr.use_count() == 2);
|
||||
co_await sleepCoro(drogon::app().getLoop(), 0.01);
|
||||
CHECK(ptr.use_count() == 1);
|
||||
});
|
||||
});
|
||||
|
||||
auto ptr = std::make_shared<std::string>("test");
|
||||
app().getLoop()->queueInLoop([ptr, TEST_CTX]() {
|
||||
async_run([ptr, TEST_CTX]() -> Task<> {
|
||||
co_await sleepCoro(drogon::app().getLoop(), 0.01);
|
||||
CHECK(ptr.use_count() == 1);
|
||||
});
|
||||
});
|
||||
|
||||
auto ptr2 = std::make_shared<std::string>("test");
|
||||
app().getLoop()->queueInLoop(async_func([ptr2, TEST_CTX]() -> Task<> {
|
||||
co_await sleepCoro(drogon::app().getLoop(), 0.01);
|
||||
CHECK(ptr2.use_count() == 1);
|
||||
}));
|
||||
}
|
||||
|
||||
DROGON_TEST(SwitchThread)
|
||||
{
|
||||
trantor::EventLoopThread thread;
|
||||
thread.getLoop()->setIndex(12345);
|
||||
thread.run();
|
||||
|
||||
auto switch_thread = [TEST_CTX, &thread]() -> Task<> {
|
||||
co_await switchThreadCoro(thread.getLoop());
|
||||
auto currentLoop = trantor::EventLoop::getEventLoopOfCurrentThread();
|
||||
MANDATE(currentLoop != nullptr);
|
||||
CHECK(currentLoop->index() == 12345);
|
||||
currentLoop->quit();
|
||||
};
|
||||
sync_wait(switch_thread());
|
||||
thread.wait();
|
||||
}
|
||||
|
||||
DROGON_TEST(Mutex)
|
||||
{
|
||||
trantor::EventLoopThreadPool pool{3};
|
||||
pool.start();
|
||||
Mutex mutex;
|
||||
async_run([&]() -> Task<> {
|
||||
co_await switchThreadCoro(pool.getLoop(0));
|
||||
auto guard = co_await mutex.scoped_lock();
|
||||
co_await sleepCoro(pool.getLoop(1), std::chrono::seconds(2));
|
||||
co_return;
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
std::promise<void> done;
|
||||
async_run([&]() -> Task<> {
|
||||
co_await switchThreadCoro(pool.getLoop(2));
|
||||
auto id = std::this_thread::get_id();
|
||||
co_await mutex.lock();
|
||||
CHECK(id == std::this_thread::get_id());
|
||||
mutex.unlock();
|
||||
CHECK(id == std::this_thread::get_id());
|
||||
done.set_value();
|
||||
co_return;
|
||||
});
|
||||
done.get_future().wait();
|
||||
for (int16_t i = 0; i < 3; i++)
|
||||
pool.getLoop(i)->quit();
|
||||
pool.wait();
|
||||
}
|
||||
|
||||
DROGON_TEST(WhenAll)
|
||||
{
|
||||
using TestCtx = std::shared_ptr<drogon::test::Case>;
|
||||
[](TestCtx TEST_CTX) -> AsyncTask {
|
||||
size_t counter = 0;
|
||||
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
|
||||
co_await drogon::sleepCoro(app().getLoop(), 0.2);
|
||||
(*counter)++;
|
||||
}(TEST_CTX, &counter);
|
||||
auto t2 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
|
||||
co_await drogon::sleepCoro(app().getLoop(), 0.1);
|
||||
(*counter)++;
|
||||
}(TEST_CTX, &counter);
|
||||
std::vector<Task<void>> tasks;
|
||||
tasks.emplace_back(std::move(t1));
|
||||
tasks.emplace_back(std::move(t2));
|
||||
|
||||
co_await when_all(std::move(tasks));
|
||||
CHECK(counter == 2);
|
||||
}(TEST_CTX);
|
||||
|
||||
[](TestCtx TEST_CTX) -> AsyncTask {
|
||||
std::vector<Task<void>> tasks;
|
||||
co_await when_all(std::move(tasks));
|
||||
SUCCESS();
|
||||
}(TEST_CTX);
|
||||
|
||||
[](TestCtx TEST_CTX) -> AsyncTask {
|
||||
auto t1 = [](TestCtx TEST_CTX) -> Task<int> { co_return 1; }(TEST_CTX);
|
||||
auto t2 = [](TestCtx TEST_CTX) -> Task<int> { co_return 2; }(TEST_CTX);
|
||||
std::vector<Task<int>> tasks;
|
||||
tasks.emplace_back(std::move(t1));
|
||||
tasks.emplace_back(std::move(t2));
|
||||
|
||||
auto res = co_await when_all(std::move(tasks));
|
||||
CO_REQUIRE(res.size() == 2);
|
||||
CHECK(res[0] == 1);
|
||||
CHECK(res[1] == 2);
|
||||
}(TEST_CTX);
|
||||
|
||||
[](TestCtx TEST_CTX) -> AsyncTask {
|
||||
auto t1 = [](TestCtx TEST_CTX) -> Task<int> { co_return 1; }(TEST_CTX);
|
||||
auto t2 = [](TestCtx TEST_CTX) -> Task<std::string> {
|
||||
co_return "Hello";
|
||||
}(TEST_CTX);
|
||||
auto [num, str] = co_await when_all(std::move(t1), std::move(t2));
|
||||
CHECK(num == 1);
|
||||
CHECK(str == "Hello");
|
||||
}(TEST_CTX);
|
||||
|
||||
[](TestCtx TEST_CTX) -> AsyncTask {
|
||||
size_t counter = 0;
|
||||
// Even on corutine throws, other coroutins run to completion
|
||||
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<int> {
|
||||
co_await drogon::sleepCoro(app().getLoop(), 0.2);
|
||||
(*counter)++;
|
||||
co_return 1;
|
||||
}(TEST_CTX, &counter);
|
||||
auto t2 = [](TestCtx TEST_CTX) -> Task<std::string> {
|
||||
co_await drogon::sleepCoro(app().getLoop(), 0.1);
|
||||
throw std::runtime_error("Test exception");
|
||||
}(TEST_CTX);
|
||||
CO_REQUIRE_THROWS(co_await when_all(std::move(t1), std::move(t2)));
|
||||
CHECK(counter == 1);
|
||||
}(TEST_CTX);
|
||||
|
||||
[](TestCtx TEST_CTX) -> AsyncTask {
|
||||
size_t counter = 0;
|
||||
// void retuens gets mapped to std::false_type in the tuple API
|
||||
auto t1 = [](TestCtx TEST_CTX, size_t *counter) -> Task<> {
|
||||
(*counter)++;
|
||||
co_return;
|
||||
}(TEST_CTX, &counter);
|
||||
auto [res] = co_await when_all(std::move(t1));
|
||||
CHECK(counter == 1);
|
||||
}(TEST_CTX);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#include <drogon/DrObject.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpController.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
class TestA : public DrObject<TestA>
|
||||
{
|
||||
};
|
||||
|
||||
namespace test
|
||||
{
|
||||
class TestB : public DrObject<TestB>
|
||||
{
|
||||
};
|
||||
} // namespace test
|
||||
|
||||
DROGON_TEST(DrObjectCreationTest)
|
||||
{
|
||||
using PtrType = std::shared_ptr<DrObjectBase>;
|
||||
auto obj = PtrType(DrClassMap::newObject("TestA"));
|
||||
CHECK(obj != nullptr);
|
||||
|
||||
auto objPtr = DrClassMap::getSingleInstance("TestA");
|
||||
CHECK(objPtr.get() != nullptr);
|
||||
|
||||
auto objPtr2 = DrClassMap::getSingleInstance<TestA>();
|
||||
CHECK(objPtr2.get() != nullptr);
|
||||
CHECK(objPtr == objPtr2);
|
||||
}
|
||||
|
||||
DROGON_TEST(DrObjectNamespaceTest)
|
||||
{
|
||||
using PtrType = std::shared_ptr<DrObjectBase>;
|
||||
auto obj = PtrType(DrClassMap::newObject("test::TestB"));
|
||||
CHECK(obj != nullptr);
|
||||
|
||||
auto objPtr = DrClassMap::getSingleInstance("test::TestB");
|
||||
CHECK(objPtr.get() != nullptr);
|
||||
|
||||
auto objPtr2 = DrClassMap::getSingleInstance<::test::TestB>();
|
||||
CHECK(objPtr2.get() != nullptr);
|
||||
CHECK(objPtr == objPtr2);
|
||||
}
|
||||
|
||||
class TestC : public DrObject<TestC>
|
||||
{
|
||||
public:
|
||||
static constexpr bool isAutoCreation = true;
|
||||
};
|
||||
|
||||
class TestD : public DrObject<TestD>
|
||||
{
|
||||
public:
|
||||
static constexpr bool isAutoCreation = false;
|
||||
};
|
||||
|
||||
class TestE : public DrObject<TestE>
|
||||
{
|
||||
public:
|
||||
static constexpr double isAutoCreation = 3.0;
|
||||
};
|
||||
|
||||
class CtrlA : public HttpController<CtrlA>
|
||||
{
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
METHOD_LIST_END
|
||||
};
|
||||
|
||||
class CtrlB : public HttpController<CtrlB, false>
|
||||
{
|
||||
public:
|
||||
METHOD_LIST_BEGIN
|
||||
METHOD_LIST_END
|
||||
};
|
||||
|
||||
DROGON_TEST(IsAutoCreationClassTest)
|
||||
{
|
||||
STATIC_REQUIRE(isAutoCreationClass<TestA>::value == false);
|
||||
STATIC_REQUIRE(isAutoCreationClass<TestC>::value == true);
|
||||
STATIC_REQUIRE(isAutoCreationClass<TestD>::value == false);
|
||||
STATIC_REQUIRE(isAutoCreationClass<TestE>::value == false);
|
||||
STATIC_REQUIRE(isAutoCreationClass<CtrlA>::value == true);
|
||||
STATIC_REQUIRE(isAutoCreationClass<CtrlB>::value == false);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#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(ContentTypeTest)
|
||||
{
|
||||
SUBSECTION(normal)
|
||||
{
|
||||
for (int i = CT_NONE + 1; i < CT_CUSTOM; i++)
|
||||
{
|
||||
auto contentType = ContentType(i);
|
||||
const auto mimeType = contentTypeToMime(contentType);
|
||||
CHECK(mimeType.empty() == false);
|
||||
CHECK(parseContentType(mimeType) == contentType);
|
||||
auto exts = getFileExtensions(contentType);
|
||||
bool shouldBeEmpty = (contentType == CT_APPLICATION_X_FORM) ||
|
||||
(contentType == CT_APPLICATION_OCTET_STREAM) ||
|
||||
(contentType == CT_MULTIPART_FORM_DATA);
|
||||
CHECK(exts.empty() == shouldBeEmpty);
|
||||
for (const auto &ext : exts)
|
||||
{
|
||||
auto dummyFile{std::string("dummy.").append(ext)};
|
||||
// Handle multiple mime types by setting the default ContentType
|
||||
if (contentType == CT_APPLICATION_X_JAVASCRIPT)
|
||||
contentType = CT_TEXT_JAVASCRIPT;
|
||||
if (contentType == CT_TEXT_XML)
|
||||
contentType = CT_APPLICATION_XML;
|
||||
CHECK(getContentType(dummyFile) == contentType);
|
||||
CHECK(parseFileType(dummyFile) != FT_UNKNOWN);
|
||||
}
|
||||
if (!shouldBeEmpty)
|
||||
{
|
||||
CHECK(getFileType(contentType) != FT_UNKNOWN);
|
||||
CHECK(getFileType(contentType) != FT_CUSTOM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SUBSECTION(negative)
|
||||
{
|
||||
CHECK(getFileType(CT_NONE) == FT_UNKNOWN);
|
||||
CHECK(getFileType(CT_CUSTOM) == FT_CUSTOM);
|
||||
CHECK(getFileType(CT_APPLICATION_X_FORM) == FT_UNKNOWN);
|
||||
CHECK(getFileType(CT_APPLICATION_OCTET_STREAM) == FT_UNKNOWN);
|
||||
CHECK(getFileType(CT_MULTIPART_FORM_DATA) == FT_UNKNOWN);
|
||||
CHECK(contentTypeToMime(CT_NONE).empty());
|
||||
CHECK(contentTypeToMime(CT_CUSTOM) ==
|
||||
contentTypeToMime(CT_APPLICATION_OCTET_STREAM));
|
||||
CHECK(parseContentType("") == CT_NONE);
|
||||
CHECK(parseContentType("application/x-www-form-urlencoded") ==
|
||||
CT_APPLICATION_X_FORM);
|
||||
CHECK(parseContentType("multipart/form-data") ==
|
||||
CT_MULTIPART_FORM_DATA);
|
||||
CHECK(parseFileType("any.thing") == FT_CUSTOM);
|
||||
CHECK(getContentType("any.thing") == CT_APPLICATION_OCTET_STREAM);
|
||||
CHECK(getFileExtensions(CT_NONE).empty());
|
||||
CHECK(getFileExtensions(CT_APPLICATION_X_FORM).empty());
|
||||
CHECK(getFileExtensions(CT_APPLICATION_OCTET_STREAM).empty());
|
||||
CHECK(getFileExtensions(CT_MULTIPART_FORM_DATA).empty());
|
||||
CHECK(getFileExtensions(CT_CUSTOM).empty());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(Gzip)
|
||||
{
|
||||
const std::string inStr =
|
||||
"Applications\n"
|
||||
"Developer\n"
|
||||
"Library\n"
|
||||
"Network\n"
|
||||
"System\n"
|
||||
"Users\n"
|
||||
"Volumes\n"
|
||||
"bin\n"
|
||||
"cores\n"
|
||||
"dev\n"
|
||||
"etc\n"
|
||||
"home\n"
|
||||
"installer.failurerequests\n"
|
||||
"net\n"
|
||||
"opt\n"
|
||||
"private\n"
|
||||
"sbin\n"
|
||||
"tmp\n"
|
||||
"usb\n"
|
||||
"usr\n"
|
||||
"var\n"
|
||||
"vm\n"
|
||||
"\n"
|
||||
"/Applications:\n"
|
||||
"Adobe\n"
|
||||
"Adobe Creative Cloud\n"
|
||||
"Adobe Photoshop CC\n"
|
||||
"AirPlayer Pro.app\n"
|
||||
"Android Studio.app\n"
|
||||
"App Store.app\n"
|
||||
"Autodesk\n"
|
||||
"Automator.app\n"
|
||||
"Axure RP Pro 7.0.app\n"
|
||||
"BaiduNetdisk_mac.app\n"
|
||||
"CLion.app\n"
|
||||
"Calculator.app\n"
|
||||
"Calendar.app\n"
|
||||
"Chess.app\n"
|
||||
"CleanApp.app\n"
|
||||
"Contacts.app\n"
|
||||
"DVD Player.app\n"
|
||||
"Dashboard.app\n"
|
||||
"Dictionary.app\n"
|
||||
"Docs for Xcode.app\n"
|
||||
"FaceTime.app\n"
|
||||
"FinalShell\n"
|
||||
"Firefox.app\n"
|
||||
"Folx.app\n"
|
||||
"Font Book.app\n"
|
||||
"GitHub.app\n"
|
||||
"Google Chrome.app\n"
|
||||
"Grammarly.app\n"
|
||||
"Image Capture.app\n"
|
||||
"Lantern.app\n"
|
||||
"Launchpad.app\n"
|
||||
"License.rtf\n"
|
||||
"MacPorts\n"
|
||||
"Mail.app\n"
|
||||
"Maps.app\n"
|
||||
"Messages.app\n"
|
||||
"Microsoft Excel.app\n"
|
||||
"Microsoft Office 2011\n"
|
||||
"Microsoft OneNote.app\n"
|
||||
"Microsoft Outlook.app\n"
|
||||
"Microsoft PowerPoint.app\n"
|
||||
"Microsoft Word.app\n"
|
||||
"Mindjet MindManager.app\n"
|
||||
"Mission Control.app\n"
|
||||
"Mockplus.app\n"
|
||||
"MyEclipse 2015\n"
|
||||
"Notes.app\n"
|
||||
"OmniGraffle.app\n"
|
||||
"Pages.app\n"
|
||||
"Photo Booth.app\n"
|
||||
"Photos.app\n"
|
||||
"Preview.app\n"
|
||||
"QJVPN.app\n"
|
||||
"QQ.app\n"
|
||||
"QuickTime Player.app\n"
|
||||
"RAR Extractor Lite.app\n"
|
||||
"Reminders.app\n"
|
||||
"Remote Desktop Connection.app\n"
|
||||
"Renee Undeleter.app\n"
|
||||
"Sabaki.app\n"
|
||||
"Safari.app\n"
|
||||
"ShadowsocksX.app\n"
|
||||
"Siri.app\n"
|
||||
"SogouInputPad.app\n"
|
||||
"Stickies.app\n"
|
||||
"System Preferences.app\n"
|
||||
"TeX\n"
|
||||
"Telegram.app\n"
|
||||
"Termius.app\n"
|
||||
"Tesumego - How to Make a Professional Go Player.app\n"
|
||||
"TextEdit.app\n"
|
||||
"Thunder.app\n"
|
||||
"Time Machine.app\n"
|
||||
"Tunnelblick.app\n"
|
||||
"Utilities\n"
|
||||
"VPN Shield.appdownload\n"
|
||||
"VirtualBox.app\n"
|
||||
"WeChat.app\n"
|
||||
"WinOnX2.app\n"
|
||||
"Wireshark.app\n"
|
||||
"Xcode.app\n"
|
||||
"Yose.app\n"
|
||||
"YoudaoNote.localized\n"
|
||||
"finalshelldata\n"
|
||||
"iBooks.app\n"
|
||||
"iPhoto.app\n"
|
||||
"iTools.app\n"
|
||||
"iTunes.app\n"
|
||||
"pgAdmin 4.app\n"
|
||||
"wechatwebdevtools.app\n"
|
||||
"\n"
|
||||
"/Applications/Adobe:\n"
|
||||
"Flash Player\n"
|
||||
"\n"
|
||||
"/Applications/Adobe/Flash Player:\n"
|
||||
"AddIns\n"
|
||||
"\n"
|
||||
"/Applications/Adobe/Flash Player/AddIns:\n"
|
||||
"airappinstaller\n"
|
||||
"\n"
|
||||
"/Applications/Adobe/Flash Player/AddIns/airappinstaller:\n"
|
||||
"airappinstaller\n"
|
||||
"digest.s\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Creative Cloud:\n"
|
||||
"Adobe Creative Cloud\n"
|
||||
"Icon\n"
|
||||
"Uninstall Adobe Creative Cloud\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC:\n"
|
||||
"Adobe Photoshop CC.app\n"
|
||||
"Configuration\n"
|
||||
"Icon\n"
|
||||
"Legal\n"
|
||||
"LegalNotices.pdf\n"
|
||||
"Locales\n"
|
||||
"Plug-ins\n"
|
||||
"Presets\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app:\n"
|
||||
"Contents\n"
|
||||
"Linguistics\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop CC.app/Contents:\n"
|
||||
"Application Data\n"
|
||||
"Frameworks\n"
|
||||
"Info.plist\n"
|
||||
"MacOS\n"
|
||||
"PkgInfo\n"
|
||||
"Required\n"
|
||||
"Resources\n"
|
||||
"_CodeSignature\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data:\n"
|
||||
"Custom File Info Panels\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info Panels:\n"
|
||||
"4.0\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info Panels/4.0:\n"
|
||||
"bin\n"
|
||||
"custom\n"
|
||||
"panels\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info Panels/4.0/bin:\n"
|
||||
"FileInfoFoundation.swf\n"
|
||||
"FileInfoUI.swf\n"
|
||||
"framework.swf\n"
|
||||
"loc\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info "
|
||||
"Panels/4.0/bin/loc:\n"
|
||||
"FileInfo_ar_AE.dat\n"
|
||||
"FileInfo_bg_BG.dat\n"
|
||||
"FileInfo_cs_CZ.dat\n"
|
||||
"FileInfo_da_DK.dat\n"
|
||||
"FileInfo_de_DE.dat\n"
|
||||
"FileInfo_el_GR.dat\n"
|
||||
"FileInfo_en_US.dat\n"
|
||||
"FileInfo_es_ES.dat\n"
|
||||
"FileInfo_et_EE.dat\n"
|
||||
"FileInfo_fi_FI.dat\n"
|
||||
"FileInfo_fr_FR.dat\n"
|
||||
"FileInfo_he_IL.dat\n"
|
||||
"FileInfo_hr_HR.dat\n"
|
||||
"FileInfo_hu_HU.dat\n"
|
||||
"FileInfo_it_IT.dat\n"
|
||||
"FileInfo_ja_JP.dat\n"
|
||||
"FileInfo_ko_KR.dat\n"
|
||||
"FileInfo_lt_LT.dat\n"
|
||||
"FileInfo_lv_LV.dat\n"
|
||||
"FileInfo_nb_NO.dat\n"
|
||||
"FileInfo_nl_NL.dat\n"
|
||||
"FileInfo_pl_PL.dat\n"
|
||||
"FileInfo_pt_BR.dat\n"
|
||||
"FileInfo_ro_RO.dat\n"
|
||||
"FileInfo_ru_RU.dat\n"
|
||||
"FileInfo_sk_SK.dat\n"
|
||||
"FileInfo_sl_SI.dat\n"
|
||||
"FileInfo_sv_SE.dat\n"
|
||||
"FileInfo_tr_TR.dat\n"
|
||||
"FileInfo_uk_UA.dat\n"
|
||||
"FileInfo_zh_CN.dat\n"
|
||||
"FileInfo_zh_TW.dat\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info "
|
||||
"Panels/4.0/custom:\n"
|
||||
"DICOM.xml\n"
|
||||
"Mobile.xml\n"
|
||||
"loc\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info "
|
||||
"Panels/4.0/custom/loc:\n"
|
||||
"DICOM_ar_AE.dat\n"
|
||||
"DICOM_bg_BG.dat\n"
|
||||
"DICOM_cs_CZ.dat\n"
|
||||
"DICOM_da_DK.dat\n"
|
||||
"DICOM_de_DE.dat\n"
|
||||
"DICOM_el_GR.dat\n"
|
||||
"DICOM_en_US.dat\n"
|
||||
"DICOM_es_ES.dat\n"
|
||||
"DICOM_et_EE.dat\n"
|
||||
"DICOM_fi_FI.dat\n"
|
||||
"DICOM_fr_FR.dat\n"
|
||||
"DICOM_he_IL.dat\n"
|
||||
"DICOM_hr_HR.dat\n"
|
||||
"DICOM_hu_HU.dat\n"
|
||||
"DICOM_it_IT.dat\n"
|
||||
"DICOM_ja_JP.dat\n"
|
||||
"DICOM_ko_KR.dat\n"
|
||||
"DICOM_lt_LT.dat\n"
|
||||
"DICOM_lv_LV.dat\n"
|
||||
"DICOM_nb_NO.dat\n"
|
||||
"DICOM_nl_NL.dat\n"
|
||||
"DICOM_pl_PL.dat\n"
|
||||
"DICOM_pt_BR.dat\n"
|
||||
"DICOM_ro_RO.dat\n"
|
||||
"DICOM_ru_RU.dat\n"
|
||||
"DICOM_sk_SK.dat\n"
|
||||
"DICOM_sl_SI.dat\n"
|
||||
"DICOM_sv_SE.dat\n"
|
||||
"DICOM_tr_TR.dat\n"
|
||||
"DICOM_uk_UA.dat\n"
|
||||
"DICOM_zh_CN.dat\n"
|
||||
"DICOM_zh_TW.dat\n"
|
||||
"Mobile_ar_AE.dat\n"
|
||||
"Mobile_bg_BG.dat\n"
|
||||
"Mobile_cs_CZ.dat\n"
|
||||
"Mobile_da_DK.dat\n"
|
||||
"Mobile_de_DE.dat\n"
|
||||
"Mobile_el_GR.dat\n"
|
||||
"Mobile_en_US.dat\n"
|
||||
"Mobile_es_ES.dat\n"
|
||||
"Mobile_et_EE.dat\n"
|
||||
"Mobile_fi_FI.dat\n"
|
||||
"Mobile_fr_FR.dat\n"
|
||||
"Mobile_he_IL.dat\n"
|
||||
"Mobile_hr_HR.dat\n"
|
||||
"Mobile_hu_HU.dat\n"
|
||||
"Mobile_it_IT.dat\n"
|
||||
"Mobile_ja_JP.dat\n"
|
||||
"Mobile_ko_KR.dat\n"
|
||||
"Mobile_lt_LT.dat\n"
|
||||
"Mobile_lv_LV.dat\n"
|
||||
"Mobile_nb_NO.dat\n"
|
||||
"Mobile_nl_NL.dat\n"
|
||||
"Mobile_pl_PL.dat\n"
|
||||
"Mobile_pt_BR.dat\n"
|
||||
"Mobile_ro_RO.dat\n"
|
||||
"Mobile_ru_RU.dat\n"
|
||||
"Mobile_sk_SK.dat\n"
|
||||
"Mobile_sl_SI.dat\n"
|
||||
"Mobile_sv_SE.dat\n"
|
||||
"Mobile_tr_TR.dat\n"
|
||||
"Mobile_uk_UA.dat\n"
|
||||
"Mobile_zh_CN.dat\n"
|
||||
"Mobile_zh_TW.dat\n"
|
||||
"\n"
|
||||
"/Applications/Adobe Photoshop CC/Adobe Photoshop "
|
||||
"CC.app/Contents/Application Data/Custom File Info "
|
||||
"Panels/4.0/panels:\n"
|
||||
"IPTC\n"
|
||||
"IPTCExt\n"
|
||||
"advanced\n"
|
||||
"audioData\n"
|
||||
"camera\n"
|
||||
"categories\n"
|
||||
"description\n"
|
||||
"dicom\n"
|
||||
"gpsData\n"
|
||||
"history\n"
|
||||
"mobile\n"
|
||||
"origin\n"
|
||||
"rawpacket";
|
||||
auto ret = utils::gzipCompress(inStr.c_str(), inStr.length());
|
||||
REQUIRE(ret.empty() == false);
|
||||
|
||||
auto decompressStr = utils::gzipDecompress(ret.data(), ret.length());
|
||||
CHECK(inStr == decompressStr);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#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() /
|
||||
trantor::Date::MICRO_SECONDS_PER_SEC ==
|
||||
1591348778);
|
||||
|
||||
// Reddit format
|
||||
date = utils::getHttpDate("Fri, 05-Jun-2020 09:19:38 GMT");
|
||||
CHECK(date.microSecondsSinceEpoch() /
|
||||
trantor::Date::MICRO_SECONDS_PER_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() /
|
||||
trantor::Date::MICRO_SECONDS_PER_SEC ==
|
||||
epoch);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "../../lib/src/HttpFileImpl.h"
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace drogon;
|
||||
using namespace std;
|
||||
|
||||
DROGON_TEST(HttpFile)
|
||||
{
|
||||
SUBSECTION(SavePathUsingDefaultConfigPath)
|
||||
{
|
||||
HttpFileImpl file;
|
||||
file.setFileName("test_file_name");
|
||||
file.setFile("test", 4);
|
||||
auto out = file.save();
|
||||
|
||||
CHECK(out == 0);
|
||||
CHECK(filesystem::exists("./uploads/test_file_name"));
|
||||
|
||||
filesystem::remove_all("./uploads/test_file_name");
|
||||
}
|
||||
|
||||
SUBSECTION(SavePathWithSpecificRelativePath)
|
||||
{
|
||||
HttpFileImpl file;
|
||||
file.setFileName("test_file_name");
|
||||
file.setFile("test", 4);
|
||||
auto out = file.save("./test_uploads_dir");
|
||||
|
||||
CHECK(out == 0);
|
||||
CHECK(filesystem::exists("./test_uploads_dir/test_file_name"));
|
||||
|
||||
filesystem::remove_all("./test_uploads_dir");
|
||||
}
|
||||
|
||||
SUBSECTION(SavePathWithSpecificAbsolutePath)
|
||||
{
|
||||
auto uploadPath = filesystem::current_path() / "test_uploads_dir";
|
||||
|
||||
HttpFileImpl file;
|
||||
file.setFileName("test_file_name");
|
||||
file.setFile("test", 4);
|
||||
auto out = file.save(uploadPath.string());
|
||||
|
||||
CHECK(out == 0);
|
||||
CHECK(filesystem::exists(uploadPath / "test_file_name"));
|
||||
|
||||
filesystem::remove_all(uploadPath.string());
|
||||
}
|
||||
|
||||
SUBSECTION(FileNameWithRelativePath)
|
||||
{
|
||||
auto uploadPath = filesystem::current_path() / "test_uploads_dir";
|
||||
|
||||
HttpFileImpl file;
|
||||
file.setFileName("../test_malicious_file_name");
|
||||
file.setFile("test", 4);
|
||||
auto out = file.save(uploadPath.string());
|
||||
|
||||
CHECK(out == -1);
|
||||
CHECK(!filesystem::exists(uploadPath / "../test_malicious_file_name"));
|
||||
|
||||
filesystem::remove_all(uploadPath);
|
||||
filesystem::remove(uploadPath / "../test_malicious_file_name");
|
||||
}
|
||||
|
||||
SUBSECTION(FileNameWithAbsolutePath)
|
||||
{
|
||||
auto fileName = filesystem::current_path() / "test_malicious_file_name";
|
||||
|
||||
HttpFileImpl file;
|
||||
file.setFileName(fileName.string());
|
||||
file.setFile("test", 4);
|
||||
auto out = file.save("./test_uploads_dir");
|
||||
|
||||
CHECK(out == -1);
|
||||
CHECK(!filesystem::exists(fileName.string()));
|
||||
|
||||
filesystem::remove_all("test_uploads_dir");
|
||||
filesystem::remove(fileName.string());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(HttpFullDateTest)
|
||||
{
|
||||
auto str = utils::getHttpFullDateStr();
|
||||
auto date = utils::getHttpDate(str);
|
||||
CHECK(utils::getHttpFullDate(date) == str);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#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") == "");
|
||||
}
|
||||
|
||||
DROGON_TEST(ResponseSetCustomContentTypeString)
|
||||
{
|
||||
auto resp = HttpResponse::newHttpResponse();
|
||||
resp->setContentTypeString("text/html");
|
||||
CHECK(resp->getContentType() == CT_TEXT_HTML);
|
||||
|
||||
resp = HttpResponse::newHttpResponse();
|
||||
resp->setContentTypeString("image/bmp");
|
||||
CHECK(resp->getContentType() == CT_IMAGE_BMP);
|
||||
|
||||
resp = HttpResponse::newHttpResponse();
|
||||
resp->setContentTypeString("thisdoesnotexist/unknown");
|
||||
CHECK(resp->getContentType() == CT_CUSTOM);
|
||||
}
|
||||
|
||||
DROGON_TEST(ResquestSetCustomContentTypeString)
|
||||
{
|
||||
auto req = HttpRequest::newHttpRequest();
|
||||
req->setContentTypeString("text/html");
|
||||
CHECK(req->getContentType() == CT_TEXT_HTML);
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setContentTypeString("image/bmp");
|
||||
CHECK(req->getContentType() == CT_IMAGE_BMP);
|
||||
|
||||
req = HttpRequest::newHttpRequest();
|
||||
req->setContentTypeString("thisdoesnotexist/unknown");
|
||||
CHECK(req->getContentType() == CT_CUSTOM);
|
||||
}
|
||||
@@ -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!!!!!!!")); // Overrides 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;");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#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");
|
||||
CHECK(drogon::utils::getMd5("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"
|
||||
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") ==
|
||||
"59F761506DFA597B0FAF1968F7CCA867");
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#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();
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
#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() == 0UL);
|
||||
buffer.append(std::string(128, 'a'));
|
||||
CHECK(buffer.readableBytes() == 128UL);
|
||||
buffer.retrieve(100);
|
||||
CHECK(buffer.readableBytes() == 28UL);
|
||||
CHECK(buffer.peekInt8() == 'a');
|
||||
buffer.retrieveAll();
|
||||
CHECK(buffer.readableBytes() == 0UL);
|
||||
}
|
||||
|
||||
SUBSECTION(writableTest)
|
||||
{
|
||||
MsgBuffer buffer(100);
|
||||
|
||||
CHECK(buffer.writableBytes() == 100UL);
|
||||
buffer.append("abcde");
|
||||
CHECK(buffer.writableBytes() == 95UL);
|
||||
buffer.append(std::string(100, 'x'));
|
||||
CHECK(buffer.writableBytes() == 111UL);
|
||||
buffer.retrieve(100);
|
||||
CHECK(buffer.writableBytes() == 111UL);
|
||||
buffer.append(std::string(112, 'c'));
|
||||
CHECK(buffer.writableBytes() == 99UL);
|
||||
buffer.retrieveAll();
|
||||
CHECK(buffer.writableBytes() == 216UL);
|
||||
}
|
||||
|
||||
SUBSECTION(addInFrontTest)
|
||||
{
|
||||
MsgBuffer buffer(100);
|
||||
|
||||
CHECK(buffer.writableBytes() == 100UL);
|
||||
buffer.addInFrontInt8('a');
|
||||
CHECK(buffer.writableBytes() == 100UL);
|
||||
buffer.addInFrontInt64(123);
|
||||
CHECK(buffer.writableBytes() == 92UL);
|
||||
buffer.addInFrontInt64(100);
|
||||
CHECK(buffer.writableBytes() == 84UL);
|
||||
buffer.addInFrontInt8(1);
|
||||
CHECK(buffer.writableBytes() == 84UL);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
#include <drogon/MultiPart.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <drogon/HttpRequest.h>
|
||||
#include "../../lib/src/MultipartStreamParser.h"
|
||||
|
||||
DROGON_TEST(MultiPartParser)
|
||||
{
|
||||
drogon::MultiPartParser parser1;
|
||||
auto req = drogon::HttpRequest::newHttpRequest();
|
||||
req->setMethod(drogon::Post);
|
||||
req->addHeader("content-type", "multipart/form-data; boundary=\"12345\"");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"somekey\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345--");
|
||||
CHECK(0 == parser1.parse(req));
|
||||
CHECK(parser1.getParameters().size() == 1);
|
||||
CHECK(parser1.getParameters().at("somekey") == "Hello; World");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"somekey\"; "
|
||||
"filename=\"test\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345--");
|
||||
drogon::MultiPartParser parser2;
|
||||
CHECK(0 == parser2.parse(req));
|
||||
auto filesMap = parser2.getFilesMap();
|
||||
CHECK(filesMap.size() == 1);
|
||||
CHECK(filesMap.at("somekey").getFileName() == "test");
|
||||
CHECK(filesMap.at("somekey").fileContent() == "Hello; World");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"name of pdf\"; "
|
||||
"filename=\"pdf-file.pdf\"\r\n"
|
||||
"Content-Type: application/octet-stream\r\n"
|
||||
"content-transfer-encoding: quoted-printable\r\n"
|
||||
"\r\n"
|
||||
"bytes of pdf file\r\n"
|
||||
"--12345--");
|
||||
drogon::MultiPartParser parser3;
|
||||
CHECK(0 == parser3.parse(req));
|
||||
filesMap = parser3.getFilesMap();
|
||||
CHECK(filesMap.size() == 1);
|
||||
CHECK(filesMap.at("name of pdf").getFileName() == "pdf-file.pdf");
|
||||
CHECK(filesMap.at("name of pdf").fileContent() == "bytes of pdf file");
|
||||
CHECK(filesMap.at("name of pdf").getContentType() ==
|
||||
drogon::CT_APPLICATION_OCTET_STREAM);
|
||||
CHECK(filesMap.at("name of pdf").getContentTransferEncoding() ==
|
||||
"quoted-printable");
|
||||
req->setBody(
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"some;key\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345--");
|
||||
drogon::MultiPartParser parser4;
|
||||
CHECK(0 == parser4.parse(req));
|
||||
CHECK(parser4.getParameters().size() == 1);
|
||||
CHECK(parser4.getParameters().at("some;key") == "Hello; World");
|
||||
}
|
||||
|
||||
DROGON_TEST(MultiPartStreamParser)
|
||||
{
|
||||
static const std::string ct = "multipart/form-data; boundary=\"12345\"";
|
||||
static const std::string_view data =
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"key1\"; filename=\"file1\"\r\n"
|
||||
"\r\n"
|
||||
"Hello; World\r\n"
|
||||
"--12345\r\n"
|
||||
"Content-Disposition: form-data; name=\"key2\"\r\n"
|
||||
"\r\n"
|
||||
"value2\r\n"
|
||||
"--12345--";
|
||||
|
||||
struct Entry
|
||||
{
|
||||
drogon::MultipartHeader header;
|
||||
std::string value;
|
||||
std::string fileContent;
|
||||
};
|
||||
|
||||
auto check = [TEST_CTX](size_t step) {
|
||||
drogon::MultipartStreamParser parser(ct);
|
||||
|
||||
auto entries = std::make_shared<std::vector<Entry>>();
|
||||
auto headerCb = [TEST_CTX, entries](drogon::MultipartHeader hdr) {
|
||||
entries->emplace_back(Entry{std::move(hdr)});
|
||||
};
|
||||
auto dataCb = [TEST_CTX, entries](const char *data, size_t length) {
|
||||
MANDATE(!entries->empty());
|
||||
if (length == 0)
|
||||
{
|
||||
// Field finished
|
||||
return;
|
||||
}
|
||||
if (entries->back().header.filename.empty())
|
||||
{
|
||||
entries->back().value.append(data, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
entries->back().fileContent.append(data, length);
|
||||
}
|
||||
};
|
||||
|
||||
size_t i = 0;
|
||||
while (i < data.length() && parser.isValid())
|
||||
{
|
||||
size_t end = i + step < data.length() ? i + step : data.length();
|
||||
parser.parse(data.data() + i, end - i, headerCb, dataCb);
|
||||
CHECK(parser.isValid());
|
||||
i = end;
|
||||
}
|
||||
MANDATE(i == data.length());
|
||||
MANDATE(parser.isFinished());
|
||||
|
||||
MANDATE(entries->size() == 2);
|
||||
CHECK(entries->at(0).header.name == "key1");
|
||||
CHECK(entries->at(0).fileContent == "Hello; World");
|
||||
CHECK(entries->at(1).header.name == "key2");
|
||||
CHECK(entries->at(1).value == "value2");
|
||||
};
|
||||
|
||||
check(1);
|
||||
check(3);
|
||||
check(7);
|
||||
check(20);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#include <drogon/utils/OStringStream.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <iostream>
|
||||
|
||||
DROGON_TEST(OStringStreamTest)
|
||||
{
|
||||
SUBSECTION(CanConvertToString)
|
||||
{
|
||||
using drogon::internal::CanConvertToString;
|
||||
|
||||
static_assert(CanConvertToString<int>::value);
|
||||
static_assert(CanConvertToString<long>::value);
|
||||
static_assert(CanConvertToString<long long>::value);
|
||||
static_assert(CanConvertToString<unsigned>::value);
|
||||
static_assert(CanConvertToString<unsigned long>::value);
|
||||
static_assert(CanConvertToString<unsigned long long>::value);
|
||||
static_assert(CanConvertToString<float>::value);
|
||||
static_assert(CanConvertToString<double>::value);
|
||||
static_assert(CanConvertToString<long double>::value);
|
||||
static_assert(!CanConvertToString<std::string>::value);
|
||||
static_assert(!CanConvertToString<std::string_view>::value);
|
||||
}
|
||||
|
||||
SUBSECTION(integer)
|
||||
{
|
||||
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(std::string_view)
|
||||
{
|
||||
drogon::OStringStream ss;
|
||||
ss << std::string_view("hello");
|
||||
ss << std::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 << std::string_view(" world");
|
||||
ss << "!";
|
||||
ss << 123;
|
||||
ss << 3.14f;
|
||||
|
||||
CHECK(ss.str() == "hello world!1233.14");
|
||||
}
|
||||
}
|
||||
@@ -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() == 1UL);
|
||||
service.unsubscribe("topic1", id);
|
||||
CHECK(service.size() == 0UL);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <string>
|
||||
|
||||
DROGON_TEST(SHA1Test)
|
||||
{
|
||||
char in[] =
|
||||
"1234567890123456789012345678901234567890123456789012345"
|
||||
"678901234567890123456789012345678901234567890";
|
||||
auto str = drogon::utils::getSha1(in, strlen((const char *)in));
|
||||
CHECK(str == "FECFD28BBC9345891A66D7C1B8FF46E60192D284");
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
#include <../../lib/src/SlashRemover.cc>
|
||||
#include <string>
|
||||
|
||||
using std::string;
|
||||
|
||||
DROGON_TEST(SlashRemoverTest)
|
||||
{
|
||||
string cleanUrl;
|
||||
|
||||
{ // Regular URL
|
||||
const string urlNoTrail = "///home//page", urlNoDup = "/home/page/",
|
||||
urlNoExcess = "/home/page";
|
||||
|
||||
SUBSECTION(Full)
|
||||
{
|
||||
const string url = "///home//page//";
|
||||
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoTrail);
|
||||
|
||||
cleanUrl = url;
|
||||
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
|
||||
CHECK(cleanUrl == urlNoDup);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
|
||||
SUBSECTION(Partial)
|
||||
{
|
||||
removeExcessiveSlashes(cleanUrl,
|
||||
findExcessiveSlashes(urlNoTrail),
|
||||
urlNoTrail);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl,
|
||||
findExcessiveSlashes(urlNoDup),
|
||||
urlNoDup);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
}
|
||||
|
||||
SUBSECTION(Root)
|
||||
{
|
||||
const string urlNoExcess = "/";
|
||||
|
||||
{ // Overlapping indices
|
||||
const string url = "//";
|
||||
|
||||
cleanUrl = url;
|
||||
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
cleanUrl = url;
|
||||
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
|
||||
{ // Intersecting indices
|
||||
const string url = "///";
|
||||
|
||||
cleanUrl = url;
|
||||
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
cleanUrl = url;
|
||||
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
}
|
||||
|
||||
SUBSECTION(Overlap)
|
||||
{
|
||||
const string urlNoDup = "/a/", urlNoExcess = "/a";
|
||||
|
||||
{ // Overlapping indices
|
||||
const string url = "/a//";
|
||||
|
||||
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
cleanUrl = url;
|
||||
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
|
||||
CHECK(cleanUrl == urlNoDup);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
|
||||
{ // Intersecting indices
|
||||
const string url = "/a///";
|
||||
|
||||
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
cleanUrl = url;
|
||||
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
|
||||
CHECK(cleanUrl == urlNoDup);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
}
|
||||
|
||||
SUBSECTION(NoTrail)
|
||||
{
|
||||
const string url = "//a", urlNoExcess = "/a";
|
||||
|
||||
cleanUrl.clear();
|
||||
{
|
||||
auto find = findTrailingSlashes(url);
|
||||
if (find != string::npos)
|
||||
removeTrailingSlashes(cleanUrl, find, url);
|
||||
}
|
||||
CHECK(cleanUrl.empty());
|
||||
|
||||
cleanUrl = url;
|
||||
removeDuplicateSlashes(cleanUrl, findDuplicateSlashes(url));
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
|
||||
SUBSECTION(NoDuplicate)
|
||||
{
|
||||
const string url = "/a/", urlNoExcess = "/a";
|
||||
|
||||
removeTrailingSlashes(cleanUrl, findTrailingSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
|
||||
cleanUrl = url;
|
||||
{
|
||||
auto find = findDuplicateSlashes(cleanUrl);
|
||||
if (find != string::npos)
|
||||
removeDuplicateSlashes(cleanUrl, find);
|
||||
}
|
||||
CHECK(cleanUrl == url);
|
||||
|
||||
cleanUrl = url;
|
||||
removeExcessiveSlashes(cleanUrl, findExcessiveSlashes(url), url);
|
||||
CHECK(cleanUrl == urlNoExcess);
|
||||
}
|
||||
|
||||
SUBSECTION(None)
|
||||
{
|
||||
const string url = "/a";
|
||||
|
||||
cleanUrl.clear();
|
||||
{
|
||||
auto find = findTrailingSlashes(url);
|
||||
if (find != string::npos)
|
||||
removeTrailingSlashes(cleanUrl, find, url);
|
||||
}
|
||||
CHECK(cleanUrl.empty());
|
||||
|
||||
cleanUrl = url;
|
||||
{
|
||||
auto find = findDuplicateSlashes(cleanUrl);
|
||||
if (find != string::npos)
|
||||
removeDuplicateSlashes(cleanUrl, find);
|
||||
}
|
||||
CHECK(cleanUrl == url);
|
||||
|
||||
cleanUrl.clear();
|
||||
{
|
||||
auto find = findExcessiveSlashes(url);
|
||||
if (find.first != string::npos || find.second != string::npos)
|
||||
removeExcessiveSlashes(cleanUrl, find, url);
|
||||
}
|
||||
CHECK(cleanUrl.empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
#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() == 4UL);
|
||||
CHECK(s.count("1") == 1UL);
|
||||
CHECK(s.count("2") == 1UL);
|
||||
CHECK(s.count("3") == 1UL);
|
||||
CHECK(s.count("4") == 1UL);
|
||||
|
||||
str = "a|||a";
|
||||
s = utils::splitStringToSet(str, "||");
|
||||
CHECK(s.size() == 2UL);
|
||||
CHECK(s.count("a") == 1UL);
|
||||
CHECK(s.count("|a") == 1UL);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include <string_view>
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <istream>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
struct ConvertibleFromStringStream
|
||||
{
|
||||
friend std::istream &operator>>(std::istream &os,
|
||||
ConvertibleFromStringStream &)
|
||||
{
|
||||
return os;
|
||||
}
|
||||
};
|
||||
|
||||
struct NotConvertibleFromStringStream
|
||||
{
|
||||
};
|
||||
|
||||
DROGON_TEST(CanConvertFromStringStream)
|
||||
{
|
||||
using drogon::internal::CanConvertFromStringStream;
|
||||
|
||||
static_assert(CanConvertFromStringStream<unsigned short>::value);
|
||||
static_assert(CanConvertFromStringStream<unsigned int>::value);
|
||||
static_assert(CanConvertFromStringStream<long>::value);
|
||||
static_assert(CanConvertFromStringStream<unsigned long>::value);
|
||||
static_assert(CanConvertFromStringStream<long long>::value);
|
||||
static_assert(CanConvertFromStringStream<unsigned long long>::value);
|
||||
static_assert(CanConvertFromStringStream<float>::value);
|
||||
static_assert(CanConvertFromStringStream<double>::value);
|
||||
static_assert(CanConvertFromStringStream<long double>::value);
|
||||
static_assert(CanConvertFromStringStream<bool>::value);
|
||||
static_assert(CanConvertFromStringStream<void *>::value);
|
||||
static_assert(CanConvertFromStringStream<short>::value);
|
||||
static_assert(CanConvertFromStringStream<int>::value);
|
||||
|
||||
static_assert(
|
||||
CanConvertFromStringStream<ConvertibleFromStringStream>::value);
|
||||
static_assert(
|
||||
!CanConvertFromStringStream<NotConvertibleFromStringStream>::value);
|
||||
}
|
||||
|
||||
struct ConstructibleFromString
|
||||
{
|
||||
ConstructibleFromString(const std::string &)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct NotConstructibleFromString
|
||||
{
|
||||
};
|
||||
|
||||
DROGON_TEST(CanConstructFromString)
|
||||
{
|
||||
using drogon::internal::CanConstructFromString;
|
||||
|
||||
static_assert(CanConstructFromString<ConstructibleFromString>::value);
|
||||
static_assert(!CanConstructFromString<NotConstructibleFromString>::value);
|
||||
static_assert(!CanConstructFromString<int>::value);
|
||||
static_assert(!CanConstructFromString<double>::value);
|
||||
static_assert(CanConstructFromString<std::string>::value);
|
||||
static_assert(CanConstructFromString<std::string_view>::value);
|
||||
static_assert(CanConstructFromString<const std::string>::value);
|
||||
static_assert(!CanConstructFromString<std::string &>::value);
|
||||
static_assert(CanConstructFromString<const std::string &>::value);
|
||||
static_assert(!CanConstructFromString<std::string *>::value);
|
||||
}
|
||||
|
||||
struct ConvertibleFromString
|
||||
{
|
||||
ConvertibleFromString &operator=(const std::string &)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
struct NotConvertibleFromString
|
||||
{
|
||||
};
|
||||
|
||||
DROGON_TEST(CanConvertFromString)
|
||||
{
|
||||
using drogon::internal::CanConvertFromString;
|
||||
|
||||
static_assert(CanConvertFromString<ConvertibleFromString>::value);
|
||||
static_assert(!CanConvertFromString<NotConvertibleFromString>::value);
|
||||
static_assert(CanConvertFromString<std::string>::value);
|
||||
static_assert(!CanConvertFromString<const std::string>::value);
|
||||
static_assert(!CanConvertFromString<const std::string &>::value);
|
||||
static_assert(!CanConvertFromString<std::string *>::value);
|
||||
static_assert(CanConvertFromString<std::string_view>::value);
|
||||
static_assert(!CanConvertFromString<const char *>::value);
|
||||
static_assert(!CanConvertFromString<std::vector<char>>::value);
|
||||
static_assert(!CanConvertFromString<std::array<char, 5>>::value);
|
||||
static_assert(!CanConvertFromString<int>::value);
|
||||
static_assert(!CanConvertFromString<double>::value);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <string_view>
|
||||
#include <drogon/utils/Utilities.h>
|
||||
#include <iostream>
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
DROGON_TEST(UuidTest)
|
||||
{
|
||||
auto uuid = drogon::utils::getUuid();
|
||||
|
||||
std::cout << "uuid: " << uuid << std::endl;
|
||||
|
||||
CHECK(uuid[8] == '-');
|
||||
CHECK(uuid[13] == '-');
|
||||
CHECK(uuid[18] == '-');
|
||||
CHECK(uuid[23] == '-');
|
||||
CHECK(uuid.size() == 36);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <drogon/drogon_test.h>
|
||||
|
||||
#include "../../lib/src/HttpRequestImpl.h"
|
||||
#include "../../lib/src/HttpResponseImpl.h"
|
||||
#include "../../lib/src/HttpControllerBinder.h"
|
||||
|
||||
using namespace drogon;
|
||||
|
||||
DROGON_TEST(WebsocketReponseTest)
|
||||
{
|
||||
WebsocketControllerBinder binder;
|
||||
|
||||
auto reqPtr = std::make_shared<HttpRequestImpl>(nullptr);
|
||||
|
||||
// Value from rfc6455-1.3
|
||||
reqPtr->addHeader("sec-websocket-key", "dGhlIHNhbXBsZSBub25jZQ==");
|
||||
|
||||
binder.handleRequest(reqPtr, [&](const HttpResponsePtr &resp) {
|
||||
CHECK(resp->statusCode() == k101SwitchingProtocols);
|
||||
CHECK(resp->headers().size() == 3);
|
||||
CHECK(resp->getHeader("Upgrade") == "websocket");
|
||||
CHECK(resp->getHeader("Connection") == "Upgrade");
|
||||
|
||||
// Value from rfc6455-1.3
|
||||
CHECK(resp->getHeader("Sec-WebSocket-Accept") ==
|
||||
"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
|
||||
|
||||
auto implPtr = std::dynamic_pointer_cast<HttpResponseImpl>(resp);
|
||||
|
||||
implPtr->makeHeaderString();
|
||||
auto buffer = implPtr->renderToBuffer();
|
||||
auto str = std::string{buffer->peek(), buffer->readableBytes()};
|
||||
|
||||
CHECK(str.find("upgrade: websocket") != std::string::npos);
|
||||
CHECK(str.find("connection: Upgrade") != std::string::npos);
|
||||
CHECK(str.find("sec-websocket-accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") !=
|
||||
std::string::npos);
|
||||
CHECK(str.find("content-length:") == std::string::npos);
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user