| //===-- llvm/unittest/Support/HTTPServer.cpp - unit tests -------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/Debuginfod/HTTPClient.h" |
| #include "llvm/Debuginfod/HTTPServer.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/ThreadPool.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| using namespace llvm; |
| |
| #ifdef LLVM_ENABLE_HTTPLIB |
| |
| TEST(HTTPServer, IsAvailable) { EXPECT_TRUE(HTTPServer::isAvailable()); } |
| |
| HTTPResponse Response = {200u, "text/plain", "hello, world\n"}; |
| std::string UrlPathPattern = R"(/(.*))"; |
| std::string InvalidUrlPathPattern = R"(/(.*)"; |
| |
| HTTPRequestHandler Handler = [](HTTPServerRequest &Request) { |
| Request.setResponse(Response); |
| }; |
| |
| HTTPRequestHandler DelayHandler = [](HTTPServerRequest &Request) { |
| std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| Request.setResponse(Response); |
| }; |
| |
| HTTPRequestHandler StreamingHandler = [](HTTPServerRequest &Request) { |
| Request.setResponse({200, "text/plain", Response.Body.size(), |
| [=](size_t Offset, size_t Length) -> StringRef { |
| return Response.Body.substr(Offset, Length); |
| }}); |
| }; |
| |
| TEST(HTTPServer, InvalidUrlPath) { |
| // test that we can bind to any address |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(InvalidUrlPathPattern, Handler), |
| Failed<StringError>()); |
| EXPECT_THAT_EXPECTED(Server.bind(), Succeeded()); |
| } |
| |
| TEST(HTTPServer, bind) { |
| // test that we can bind to any address |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded()); |
| EXPECT_THAT_EXPECTED(Server.bind(), Succeeded()); |
| } |
| |
| TEST(HTTPServer, ListenBeforeBind) { |
| // test that we can bind to any address |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded()); |
| EXPECT_THAT_ERROR(Server.listen(), Failed<StringError>()); |
| } |
| |
| #ifdef LLVM_ENABLE_CURL |
| // Test the client and server against each other. |
| |
| // Test fixture to initialize and teardown the HTTP client for each |
| // client-server test |
| class HTTPClientServerTest : public ::testing::Test { |
| protected: |
| void SetUp() override { HTTPClient::initialize(); } |
| void TearDown() override { HTTPClient::cleanup(); } |
| }; |
| |
| /// A simple handler which writes returned data to a string. |
| struct StringHTTPResponseHandler final : public HTTPResponseHandler { |
| std::string ResponseBody = ""; |
| /// These callbacks store the body and status code in an HTTPResponseBuffer |
| /// allocated based on Content-Length. The Content-Length header must be |
| /// handled by handleHeaderLine before any calls to handleBodyChunk. |
| Error handleBodyChunk(StringRef BodyChunk) override { |
| ResponseBody = ResponseBody + BodyChunk.str(); |
| return Error::success(); |
| } |
| }; |
| |
| TEST_F(HTTPClientServerTest, Hello) { |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded()); |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port); |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Handler.ResponseBody, Response.Body); |
| EXPECT_EQ(Client.responseCode(), Response.Code); |
| Server.stop(); |
| } |
| |
| TEST_F(HTTPClientServerTest, LambdaHandlerHello) { |
| HTTPServer Server; |
| HTTPResponse LambdaResponse = {200u, "text/plain", |
| "hello, world from a lambda\n"}; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, |
| [LambdaResponse](HTTPServerRequest &Request) { |
| Request.setResponse(LambdaResponse); |
| }), |
| Succeeded()); |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port); |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Handler.ResponseBody, LambdaResponse.Body); |
| EXPECT_EQ(Client.responseCode(), LambdaResponse.Code); |
| Server.stop(); |
| } |
| |
| // Test the streaming response. |
| TEST_F(HTTPClientServerTest, StreamingHello) { |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, StreamingHandler), Succeeded()); |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port); |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Handler.ResponseBody, Response.Body); |
| EXPECT_EQ(Client.responseCode(), Response.Code); |
| Server.stop(); |
| } |
| |
| // Writes a temporary file and streams it back using streamFile. |
| HTTPRequestHandler TempFileStreamingHandler = [](HTTPServerRequest Request) { |
| int FD; |
| SmallString<64> TempFilePath; |
| sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD, |
| TempFilePath); |
| raw_fd_ostream OS(FD, true, /*unbuffered=*/true); |
| OS << Response.Body; |
| OS.close(); |
| streamFile(Request, TempFilePath); |
| }; |
| |
| // Test streaming back chunks of a file. |
| TEST_F(HTTPClientServerTest, StreamingFileResponse) { |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, TempFileStreamingHandler), |
| Succeeded()); |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port); |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Handler.ResponseBody, Response.Body); |
| EXPECT_EQ(Client.responseCode(), Response.Code); |
| Server.stop(); |
| } |
| |
| // Deletes the temporary file before streaming it back, should give a 404 not |
| // found status code. |
| HTTPRequestHandler MissingTempFileStreamingHandler = |
| [](HTTPServerRequest Request) { |
| int FD; |
| SmallString<64> TempFilePath; |
| sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD, |
| TempFilePath); |
| raw_fd_ostream OS(FD, true, /*unbuffered=*/true); |
| OS << Response.Body; |
| OS.close(); |
| // delete the file |
| sys::fs::remove(TempFilePath); |
| streamFile(Request, TempFilePath); |
| }; |
| |
| // Streaming a missing file should give a 404. |
| TEST_F(HTTPClientServerTest, StreamingMissingFileResponse) { |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, MissingTempFileStreamingHandler), |
| Succeeded()); |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port); |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Client.responseCode(), 404u); |
| Server.stop(); |
| } |
| |
| TEST_F(HTTPClientServerTest, ClientTimeout) { |
| HTTPServer Server; |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, DelayHandler), Succeeded()); |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port); |
| HTTPClient Client; |
| // Timeout below 50ms, request should fail |
| Client.setTimeout(std::chrono::milliseconds(40)); |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Failed<StringError>()); |
| Server.stop(); |
| } |
| |
| // Check that Url paths are dispatched to the first matching handler and provide |
| // the correct path pattern match components. |
| TEST_F(HTTPClientServerTest, PathMatching) { |
| HTTPServer Server; |
| |
| EXPECT_THAT_ERROR( |
| Server.get(R"(/abc/(.*)/(.*))", |
| [&](HTTPServerRequest &Request) { |
| EXPECT_EQ(Request.UrlPath, "/abc/1/2"); |
| ASSERT_THAT(Request.UrlPathMatches, |
| testing::ElementsAre("1", "2")); |
| Request.setResponse({200u, "text/plain", Request.UrlPath}); |
| }), |
| Succeeded()); |
| EXPECT_THAT_ERROR(Server.get(UrlPathPattern, |
| [&](HTTPServerRequest &Request) { |
| llvm_unreachable( |
| "Should not reach this handler"); |
| Handler(Request); |
| }), |
| Succeeded()); |
| |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2"; |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Handler.ResponseBody, "/abc/1/2"); |
| EXPECT_EQ(Client.responseCode(), 200u); |
| Server.stop(); |
| } |
| |
| TEST_F(HTTPClientServerTest, FirstPathMatched) { |
| HTTPServer Server; |
| |
| EXPECT_THAT_ERROR( |
| Server.get(UrlPathPattern, |
| [&](HTTPServerRequest Request) { Handler(Request); }), |
| Succeeded()); |
| |
| EXPECT_THAT_ERROR( |
| Server.get(R"(/abc/(.*)/(.*))", |
| [&](HTTPServerRequest Request) { |
| EXPECT_EQ(Request.UrlPathMatches.size(), 2u); |
| llvm_unreachable("Should not reach this handler"); |
| Request.setResponse({200u, "text/plain", Request.UrlPath}); |
| }), |
| Succeeded()); |
| |
| Expected<unsigned> PortOrErr = Server.bind(); |
| EXPECT_THAT_EXPECTED(PortOrErr, Succeeded()); |
| unsigned Port = *PortOrErr; |
| ThreadPool Pool(hardware_concurrency(1)); |
| Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); }); |
| std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2"; |
| HTTPRequest Request(Url); |
| StringHTTPResponseHandler Handler; |
| HTTPClient Client; |
| EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded()); |
| EXPECT_EQ(Handler.ResponseBody, Response.Body); |
| EXPECT_EQ(Client.responseCode(), Response.Code); |
| Server.stop(); |
| } |
| |
| #endif |
| |
| #else |
| |
| TEST(HTTPServer, IsAvailable) { EXPECT_FALSE(HTTPServer::isAvailable()); } |
| |
| #endif // LLVM_ENABLE_HTTPLIB |