// Copyright Epic Games, Inc. All Rights Reserved. #define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING #if ZEN_WITH_TESTS # define ZEN_TEST_WITH_RUNNER 1 # include "zenserver-test.h" # include # include # include # include # include # include # include # include # include # include # include # include # include # include # if ZEN_PLATFORM_WINDOWS # include # include # else # include struct Concurrency { template static void parallel_invoke(T&&... t) { constexpr size_t NumTs = sizeof...(t); std::thread Threads[NumTs] = { std::thread(std::forward(t))..., }; for (std::thread& Thread : Threads) { Thread.join(); } } }; # endif # include ////////////////////////////////////////////////////////////////////////// using namespace std::literals; ////////////////////////////////////////////////////////////////////////// namespace zen::tests { zen::ZenServerEnvironment TestEnv; } int main(int argc, char** argv) { using namespace std::literals; using namespace zen; # if ZEN_PLATFORM_LINUX IgnoreChildSignals(); # endif # if ZEN_WITH_TRACE zen::TraceInit("zenserver-test"); TraceOptions TraceCommandlineOptions; if (GetTraceOptionsFromCommandline(TraceCommandlineOptions)) { TraceConfigure(TraceCommandlineOptions); } # endif // ZEN_WITH_TRACE zen::logging::InitializeLogging(); zen::logging::SetLogLevel(zen::logging::level::Debug); spdlog::set_formatter(std::make_unique("test", std::chrono::system_clock::now())); std::filesystem::path ProgramBaseDir = GetRunningExecutablePath().parent_path(); std::filesystem::path TestBaseDir = std::filesystem::current_path() / ".test"; // This is pretty janky because we're passing most of the options through to the test // framework, so we can't just use cxxopts (I think). This should ideally be cleaned up // somehow in the future std::string ServerClass; for (int i = 1; i < argc; ++i) { if (argv[i] == "--http"sv) { if ((i + 1) < argc) { ServerClass = argv[++i]; } } } zen::tests::TestEnv.InitializeForTest(ProgramBaseDir, TestBaseDir, ServerClass); ZEN_INFO("Running tests...(base dir: '{}')", TestBaseDir); zen::testing::TestRunner Runner; Runner.ApplyCommandLine(argc, argv); return Runner.Run(); } namespace zen::tests { TEST_CASE("default.single") { std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); ZenServerInstance Instance(TestEnv); Instance.SetTestDir(TestDir); const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady(); std::atomic RequestCount{0}; std::atomic BatchCounter{0}; ZEN_INFO("Running single server test..."); auto IssueTestRequests = [&] { const uint64_t BatchNo = BatchCounter.fetch_add(1); const int ThreadId = zen::GetCurrentThreadId(); ZEN_INFO("query batch {} started (thread {})", BatchNo, ThreadId); HttpClient Http{fmt::format("http://localhost:{}", PortNumber)}; for (int i = 0; i < 100; ++i) { auto res = Http.Get("/test/hello"sv); ++RequestCount; } ZEN_INFO("query batch {} ended (thread {})", BatchNo, ThreadId); }; zen::Stopwatch timer; Concurrency::parallel_invoke(IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests, IssueTestRequests); uint64_t Elapsed = timer.GetElapsedTimeMs(); ZEN_INFO("{} requests in {} ({})", RequestCount.load(), zen::NiceTimeSpanMs(Elapsed), zen::NiceRate(RequestCount, (uint32_t)Elapsed, "req")); } TEST_CASE("default.loopback") { std::filesystem::path TestDir = TestEnv.CreateNewTestDir(); ZenServerInstance Instance(TestEnv); Instance.SetTestDir(TestDir); const uint16_t PortNumber = Instance.SpawnServerAndWaitUntilReady("--http-forceloopback"); ZEN_INFO("Running loopback server test..."); SUBCASE("ipv4 endpoint connectivity") { HttpClient Http{fmt::format("http://127.0.0.1:{}", PortNumber)}; auto res = Http.Get("/test/hello"sv); CHECK(res); } SUBCASE("ipv6 endpoint connectivity") { HttpClient Http{fmt::format("http://[::1]:{}", PortNumber)}; auto res = Http.Get("/test/hello"sv); CHECK(res); } } TEST_CASE("multi.basic") { ZenServerInstance Instance1(TestEnv); std::filesystem::path TestDir1 = TestEnv.CreateNewTestDir(); Instance1.SetTestDir(TestDir1); Instance1.SpawnServer(); ZenServerInstance Instance2(TestEnv); std::filesystem::path TestDir2 = TestEnv.CreateNewTestDir(); Instance2.SetTestDir(TestDir2); Instance2.SpawnServer(); ZEN_INFO("Waiting..."); const uint16_t PortNum1 = Instance1.WaitUntilReady(); CHECK_MESSAGE(PortNum1 != 0, Instance1.GetLogOutput()); const uint16_t PortNum2 = Instance2.WaitUntilReady(); CHECK_MESSAGE(PortNum2 != 0, Instance1.GetLogOutput()); std::atomic RequestCount{0}; std::atomic BatchCounter{0}; auto IssueTestRequests = [&](int PortNumber) { const uint64_t BatchNo = BatchCounter.fetch_add(1); const int ThreadId = zen::GetCurrentThreadId(); ZEN_INFO("query batch {} started (thread {}) for port {}", BatchNo, ThreadId, PortNumber); HttpClient Http{fmt::format("http://localhost:{}", PortNumber)}; for (int i = 0; i < 100; ++i) { auto res = Http.Get("/test/hello"sv); ++RequestCount; } ZEN_INFO("query batch {} ended (thread {})", BatchNo, ThreadId); }; zen::Stopwatch timer; ZEN_INFO("Running multi-server test..."); Concurrency::parallel_invoke([&] { IssueTestRequests(PortNum1); }, [&] { IssueTestRequests(PortNum2); }, [&] { IssueTestRequests(PortNum1); }, [&] { IssueTestRequests(PortNum2); }); uint64_t Elapsed = timer.GetElapsedTimeMs(); ZEN_INFO("{} requests in {} ({})", RequestCount.load(), zen::NiceTimeSpanMs(Elapsed), zen::NiceRate(RequestCount, (uint32_t)Elapsed, "req")); } TEST_CASE("http.basics") { using namespace std::literals; ZenServerTestHelper Servers{"http.basics"sv, 1}; Servers.SpawnServers(); ZenServerInstance& Instance = Servers.GetInstance(0); const std::string BaseUri = Instance.GetBaseUri(); HttpClient Http{BaseUri}; { HttpClient::Response r = Http.Get("/testing/hello"); CHECK(r); } { HttpClient::Response r = Http.Post("/testing/hello"); CHECK_EQ(r.StatusCode, HttpResponseCode::NotFound); } { IoBuffer Body{IoBuffer::Wrap, "yoyoyoyo", 8}; HttpClient::Response r = Http.Post("/testing/echo", Body); CHECK_EQ(r.StatusCode, HttpResponseCode::OK); CHECK(r.ResponsePayload.GetView().EqualBytes(Body.GetView())); } } TEST_CASE("http.package") { using namespace std::literals; ZenServerTestHelper Servers{"http.package"sv, 1}; Servers.SpawnServers(); ZenServerInstance& Instance = Servers.GetInstance(0); const std::string BaseUri = Instance.GetBaseUri(); static const uint8_t Data1[] = {0, 1, 2, 3}; static const uint8_t Data2[] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; zen::CompressedBuffer AttachmentData1 = zen::CompressedBuffer::Compress(zen::SharedBuffer::Clone({Data1, 4}), zen::OodleCompressor::NotSet, zen::OodleCompressionLevel::None); zen::CbAttachment Attach1{AttachmentData1, AttachmentData1.DecodeRawHash()}; zen::CompressedBuffer AttachmentData2 = zen::CompressedBuffer::Compress(zen::SharedBuffer::Clone({Data2, 8}), zen::OodleCompressor::NotSet, zen::OodleCompressionLevel::None); zen::CbAttachment Attach2{AttachmentData2, AttachmentData2.DecodeRawHash()}; zen::CbObjectWriter Writer; Writer.AddAttachment("attach1", Attach1); Writer.AddAttachment("attach2", Attach2); zen::CbObject CoreObject = Writer.Save(); zen::CbPackage TestPackage; TestPackage.SetObject(CoreObject); TestPackage.AddAttachment(Attach1); TestPackage.AddAttachment(Attach2); zen::HttpClient TestClient(BaseUri); zen::HttpClient::Response Response = TestClient.TransactPackage("/testing/package"sv, TestPackage); zen::CbPackage ResponsePackage = ParsePackageMessage(Response.ResponsePayload); CHECK_EQ(ResponsePackage, TestPackage); } # if 0 TEST_CASE("lifetime.owner") { // This test is designed to verify that the hand-over of sponsor processes is handled // correctly for the case when a second or third process is launched on the same port // // Due to the nature of it, it cannot be const uint16_t PortNumber = 23456; ZenServerInstance Zen1(TestEnv); std::filesystem::path TestDir1 = TestEnv.CreateNewTestDir(); Zen1.SetTestDir(TestDir1); Zen1.SpawnServer(PortNumber); Zen1.WaitUntilReady(); Zen1.Detach(); ZenServerInstance Zen2(TestEnv); std::filesystem::path TestDir2 = TestEnv.CreateNewTestDir(); Zen2.SetTestDir(TestDir2); Zen2.SpawnServer(PortNumber); Zen2.WaitUntilReady(); Zen2.Detach(); } TEST_CASE("lifetime.owner.2") { // This test is designed to verify that the hand-over of sponsor processes is handled // correctly for the case when a second or third process is launched on the same port // // Due to the nature of it, it cannot be const uint16_t PortNumber = 13456; std::filesystem::path TestDir1 = TestEnv.CreateNewTestDir(); std::filesystem::path TestDir2 = TestEnv.CreateNewTestDir(); ZenServerInstance Zen1(TestEnv); Zen1.SetTestDir(TestDir1); Zen1.SpawnServer(PortNumber); Zen1.WaitUntilReady(); ZenServerInstance Zen2(TestEnv); Zen2.SetTestDir(TestDir2); Zen2.SetOwnerPid(Zen1.GetPid()); Zen2.SpawnServer(PortNumber + 1); Zen2.Detach(); ZenServerInstance Zen3(TestEnv); Zen3.SetTestDir(TestDir2); Zen3.SetOwnerPid(Zen1.GetPid()); Zen3.SpawnServer(PortNumber + 1); Zen3.Detach(); ZenServerInstance Zen4(TestEnv); Zen4.SetTestDir(TestDir2); Zen4.SetOwnerPid(Zen1.GetPid()); Zen4.SpawnServer(PortNumber + 1); Zen4.Detach(); } # endif } // namespace zen::tests #else int main() { } #endif