1//===-- llvm/unittest/Support/HTTPServer.cpp - unit tests -------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "llvm/ADT/StringExtras.h"
10#include "llvm/Debuginfod/HTTPClient.h"
11#include "llvm/Debuginfod/HTTPServer.h"
12#include "llvm/Support/Error.h"
13#include "llvm/Support/ThreadPool.h"
14#include "llvm/Testing/Support/Error.h"
15#include "gmock/gmock.h"
16#include "gtest/gtest.h"
17
18using namespace llvm;
19
20#ifdef LLVM_ENABLE_HTTPLIB
21
22TEST(HTTPServer, IsAvailable) { EXPECT_TRUE(HTTPServer::isAvailable()); }
23
24HTTPResponse Response = {200u, "text/plain", "hello, world\n"};
25std::string UrlPathPattern = R"(/(.*))";
26std::string InvalidUrlPathPattern = R"(/(.*)";
27
28HTTPRequestHandler Handler = [](HTTPServerRequest &Request) {
29 Request.setResponse(Response);
30};
31
32HTTPRequestHandler DelayHandler = [](HTTPServerRequest &Request) {
33 std::this_thread::sleep_for(std::chrono::milliseconds(50));
34 Request.setResponse(Response);
35};
36
37HTTPRequestHandler StreamingHandler = [](HTTPServerRequest &Request) {
38 Request.setResponse({200, "text/plain", Response.Body.size(),
39 [=](size_t Offset, size_t Length) -> StringRef {
40 return Response.Body.substr(Offset, Length);
41 }});
42};
43
44TEST(HTTPServer, InvalidUrlPath) {
45 // test that we can bind to any address
46 HTTPServer Server;
47 EXPECT_THAT_ERROR(Server.get(InvalidUrlPathPattern, Handler),
48 Failed<StringError>());
49 EXPECT_THAT_EXPECTED(Server.bind(), Succeeded());
50}
51
52TEST(HTTPServer, bind) {
53 // test that we can bind to any address
54 HTTPServer Server;
55 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded());
56 EXPECT_THAT_EXPECTED(Server.bind(), Succeeded());
57}
58
59TEST(HTTPServer, ListenBeforeBind) {
60 // test that we can bind to any address
61 HTTPServer Server;
62 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded());
63 EXPECT_THAT_ERROR(Server.listen(), Failed<StringError>());
64}
65
66#ifdef LLVM_ENABLE_CURL
67// Test the client and server against each other.
68
69// Test fixture to initialize and teardown the HTTP client for each
70// client-server test
71class HTTPClientServerTest : public ::testing::Test {
72protected:
73 void SetUp() override { HTTPClient::initialize(); }
74 void TearDown() override { HTTPClient::cleanup(); }
75};
76
77/// A simple handler which writes returned data to a string.
78struct StringHTTPResponseHandler final : public HTTPResponseHandler {
79 std::string ResponseBody = "";
80 /// These callbacks store the body and status code in an HTTPResponseBuffer
81 /// allocated based on Content-Length. The Content-Length header must be
82 /// handled by handleHeaderLine before any calls to handleBodyChunk.
83 Error handleBodyChunk(StringRef BodyChunk) override {
84 ResponseBody = ResponseBody + BodyChunk.str();
85 return Error::success();
86 }
87};
88
89TEST_F(HTTPClientServerTest, Hello) {
90 HTTPServer Server;
91 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded());
92 Expected<unsigned> PortOrErr = Server.bind();
93 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
94 unsigned Port = *PortOrErr;
95 DefaultThreadPool Pool(hardware_concurrency(1));
96 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
97 std::string Url = "http://localhost:" + utostr(Port);
98 HTTPRequest Request(Url);
99 StringHTTPResponseHandler Handler;
100 HTTPClient Client;
101 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
102 EXPECT_EQ(Handler.ResponseBody, Response.Body);
103 EXPECT_EQ(Client.responseCode(), Response.Code);
104 Server.stop();
105}
106
107TEST_F(HTTPClientServerTest, LambdaHandlerHello) {
108 HTTPServer Server;
109 HTTPResponse LambdaResponse = {200u, "text/plain",
110 "hello, world from a lambda\n"};
111 EXPECT_THAT_ERROR(Server.get(UrlPathPattern,
112 [LambdaResponse](HTTPServerRequest &Request) {
113 Request.setResponse(LambdaResponse);
114 }),
115 Succeeded());
116 Expected<unsigned> PortOrErr = Server.bind();
117 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
118 unsigned Port = *PortOrErr;
119 DefaultThreadPool Pool(hardware_concurrency(1));
120 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
121 std::string Url = "http://localhost:" + utostr(Port);
122 HTTPRequest Request(Url);
123 StringHTTPResponseHandler Handler;
124 HTTPClient Client;
125 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
126 EXPECT_EQ(Handler.ResponseBody, LambdaResponse.Body);
127 EXPECT_EQ(Client.responseCode(), LambdaResponse.Code);
128 Server.stop();
129}
130
131// Test the streaming response.
132TEST_F(HTTPClientServerTest, StreamingHello) {
133 HTTPServer Server;
134 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, StreamingHandler), Succeeded());
135 Expected<unsigned> PortOrErr = Server.bind();
136 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
137 unsigned Port = *PortOrErr;
138 DefaultThreadPool Pool(hardware_concurrency(1));
139 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
140 std::string Url = "http://localhost:" + utostr(Port);
141 HTTPRequest Request(Url);
142 StringHTTPResponseHandler Handler;
143 HTTPClient Client;
144 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
145 EXPECT_EQ(Handler.ResponseBody, Response.Body);
146 EXPECT_EQ(Client.responseCode(), Response.Code);
147 Server.stop();
148}
149
150// Writes a temporary file and streams it back using streamFile.
151HTTPRequestHandler TempFileStreamingHandler = [](HTTPServerRequest Request) {
152 int FD;
153 SmallString<64> TempFilePath;
154 sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD,
155 TempFilePath);
156 raw_fd_ostream OS(FD, true, /*unbuffered=*/true);
157 OS << Response.Body;
158 OS.close();
159 streamFile(Request, TempFilePath);
160};
161
162// Test streaming back chunks of a file.
163TEST_F(HTTPClientServerTest, StreamingFileResponse) {
164 HTTPServer Server;
165 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, TempFileStreamingHandler),
166 Succeeded());
167 Expected<unsigned> PortOrErr = Server.bind();
168 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
169 unsigned Port = *PortOrErr;
170 DefaultThreadPool Pool(hardware_concurrency(1));
171 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
172 std::string Url = "http://localhost:" + utostr(Port);
173 HTTPRequest Request(Url);
174 StringHTTPResponseHandler Handler;
175 HTTPClient Client;
176 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
177 EXPECT_EQ(Handler.ResponseBody, Response.Body);
178 EXPECT_EQ(Client.responseCode(), Response.Code);
179 Server.stop();
180}
181
182// Deletes the temporary file before streaming it back, should give a 404 not
183// found status code.
184HTTPRequestHandler MissingTempFileStreamingHandler =
185 [](HTTPServerRequest Request) {
186 int FD;
187 SmallString<64> TempFilePath;
188 sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD,
189 TempFilePath);
190 raw_fd_ostream OS(FD, true, /*unbuffered=*/true);
191 OS << Response.Body;
192 OS.close();
193 // delete the file
194 sys::fs::remove(TempFilePath);
195 streamFile(Request, TempFilePath);
196 };
197
198// Streaming a missing file should give a 404.
199TEST_F(HTTPClientServerTest, StreamingMissingFileResponse) {
200 HTTPServer Server;
201 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, MissingTempFileStreamingHandler),
202 Succeeded());
203 Expected<unsigned> PortOrErr = Server.bind();
204 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
205 unsigned Port = *PortOrErr;
206 DefaultThreadPool Pool(hardware_concurrency(1));
207 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
208 std::string Url = "http://localhost:" + utostr(Port);
209 HTTPRequest Request(Url);
210 StringHTTPResponseHandler Handler;
211 HTTPClient Client;
212 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
213 EXPECT_EQ(Client.responseCode(), 404u);
214 Server.stop();
215}
216
217TEST_F(HTTPClientServerTest, ClientTimeout) {
218 HTTPServer Server;
219 EXPECT_THAT_ERROR(Server.get(UrlPathPattern, DelayHandler), Succeeded());
220 Expected<unsigned> PortOrErr = Server.bind();
221 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
222 unsigned Port = *PortOrErr;
223 DefaultThreadPool Pool(hardware_concurrency(1));
224 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
225 std::string Url = "http://localhost:" + utostr(Port);
226 HTTPClient Client;
227 // Timeout below 50ms, request should fail
228 Client.setTimeout(std::chrono::milliseconds(40));
229 HTTPRequest Request(Url);
230 StringHTTPResponseHandler Handler;
231 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Failed<StringError>());
232 Server.stop();
233}
234
235// Check that Url paths are dispatched to the first matching handler and provide
236// the correct path pattern match components.
237TEST_F(HTTPClientServerTest, PathMatching) {
238 HTTPServer Server;
239
240 EXPECT_THAT_ERROR(
241 Server.get(R"(/abc/(.*)/(.*))",
242 [&](HTTPServerRequest &Request) {
243 EXPECT_EQ(Request.UrlPath, "/abc/1/2");
244 ASSERT_THAT(Request.UrlPathMatches,
245 testing::ElementsAre("1", "2"));
246 Request.setResponse({200u, "text/plain", Request.UrlPath});
247 }),
248 Succeeded());
249 EXPECT_THAT_ERROR(Server.get(UrlPathPattern,
250 [&](HTTPServerRequest &Request) {
251 llvm_unreachable(
252 "Should not reach this handler");
253 Handler(Request);
254 }),
255 Succeeded());
256
257 Expected<unsigned> PortOrErr = Server.bind();
258 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
259 unsigned Port = *PortOrErr;
260 DefaultThreadPool Pool(hardware_concurrency(1));
261 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
262 std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2";
263 HTTPRequest Request(Url);
264 StringHTTPResponseHandler Handler;
265 HTTPClient Client;
266 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
267 EXPECT_EQ(Handler.ResponseBody, "/abc/1/2");
268 EXPECT_EQ(Client.responseCode(), 200u);
269 Server.stop();
270}
271
272TEST_F(HTTPClientServerTest, FirstPathMatched) {
273 HTTPServer Server;
274
275 EXPECT_THAT_ERROR(
276 Server.get(UrlPathPattern,
277 [&](HTTPServerRequest Request) { Handler(Request); }),
278 Succeeded());
279
280 EXPECT_THAT_ERROR(
281 Server.get(R"(/abc/(.*)/(.*))",
282 [&](HTTPServerRequest Request) {
283 EXPECT_EQ(Request.UrlPathMatches.size(), 2u);
284 llvm_unreachable("Should not reach this handler");
285 Request.setResponse({200u, "text/plain", Request.UrlPath});
286 }),
287 Succeeded());
288
289 Expected<unsigned> PortOrErr = Server.bind();
290 EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
291 unsigned Port = *PortOrErr;
292 DefaultThreadPool Pool(hardware_concurrency(1));
293 Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
294 std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2";
295 HTTPRequest Request(Url);
296 StringHTTPResponseHandler Handler;
297 HTTPClient Client;
298 EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
299 EXPECT_EQ(Handler.ResponseBody, Response.Body);
300 EXPECT_EQ(Client.responseCode(), Response.Code);
301 Server.stop();
302}
303
304#endif
305
306#else
307
308TEST(HTTPServer, IsAvailable) { EXPECT_FALSE(HTTPServer::isAvailable()); }
309
310#endif // LLVM_ENABLE_HTTPLIB
311

source code of llvm/unittests/Debuginfod/HTTPServerTests.cpp