libstore: add filetranfer retry handling tests
we did not have any, despite retry handling being somewhat complex.
Change-Id: I5051a1c0a3861849ff67f512b33f6d3dda12cc95
This commit is contained in:
parent
fb85228755
commit
40be91afbf
|
@ -28,24 +28,33 @@ namespace {
|
||||||
struct Reply {
|
struct Reply {
|
||||||
std::string status, headers;
|
std::string status, headers;
|
||||||
std::function<std::optional<std::string>(int)> content;
|
std::function<std::optional<std::string>(int)> content;
|
||||||
|
std::list<std::string> expectedHeaders;
|
||||||
|
|
||||||
Reply(
|
Reply(
|
||||||
std::string_view status, std::string_view headers, std::function<std::string()> content
|
std::string_view status,
|
||||||
|
std::string_view headers,
|
||||||
|
std::function<std::string()> content,
|
||||||
|
std::list<std::string> expectedHeaders = {}
|
||||||
|
)
|
||||||
|
: Reply(
|
||||||
|
status,
|
||||||
|
headers,
|
||||||
|
[content](int round) { return round == 0 ? std::optional(content()) : std::nullopt; },
|
||||||
|
std::move(expectedHeaders)
|
||||||
)
|
)
|
||||||
: Reply(status, headers, [content](int round) {
|
|
||||||
return round == 0 ? std::optional(content()) : std::nullopt;
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Reply(
|
Reply(
|
||||||
std::string_view status,
|
std::string_view status,
|
||||||
std::string_view headers,
|
std::string_view headers,
|
||||||
std::function<std::optional<std::string>(int)> content
|
std::function<std::optional<std::string>(int)> content,
|
||||||
|
std::list<std::string> expectedHeaders = {}
|
||||||
)
|
)
|
||||||
: status(status)
|
: status(status)
|
||||||
, headers(headers)
|
, headers(headers)
|
||||||
, content(content)
|
, content(content)
|
||||||
|
, expectedHeaders(std::move(expectedHeaders))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -124,6 +133,25 @@ serveHTTP(std::vector<Reply> replies)
|
||||||
send("HTTP/1.1 ");
|
send("HTTP/1.1 ");
|
||||||
send(reply.status);
|
send(reply.status);
|
||||||
send("\r\n");
|
send("\r\n");
|
||||||
|
|
||||||
|
std::string requestWithHeaders;
|
||||||
|
while (true) {
|
||||||
|
char c;
|
||||||
|
if (recv(conn.get(), &c, 1, MSG_NOSIGNAL) != 1) {
|
||||||
|
debug("recv() failed for headers: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requestWithHeaders += c;
|
||||||
|
if (requestWithHeaders.ends_with("\r\n\r\n")) {
|
||||||
|
requestWithHeaders.resize(requestWithHeaders.size() - 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug("got request:\n%s", requestWithHeaders);
|
||||||
|
for (auto & expected : reply.expectedHeaders) {
|
||||||
|
ASSERT_TRUE(requestWithHeaders.contains(fmt("%s\r\n", expected)));
|
||||||
|
}
|
||||||
|
|
||||||
send(reply.headers);
|
send(reply.headers);
|
||||||
send("\r\n");
|
send("\r\n");
|
||||||
for (int round = 0; ; round++) {
|
for (int round = 0; ; round++) {
|
||||||
|
@ -288,4 +316,57 @@ TEST(FileTransfer, stalledReaderDoesntBlockOthers)
|
||||||
ASSERT_THROW(drop(*data2, 1), EndOfFile);
|
ASSERT_THROW(drop(*data2, 1), EndOfFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FileTransfer, retries)
|
||||||
|
{
|
||||||
|
auto [port, srv] = serveHTTP({
|
||||||
|
// transient setup failure
|
||||||
|
{"429 try again later", "content-length: 0\r\n", [] { return ""; }},
|
||||||
|
// transient transfer failure (simulates a connection break)
|
||||||
|
{"200 ok",
|
||||||
|
"content-length: 2\r\n"
|
||||||
|
"accept-ranges: bytes\r\n",
|
||||||
|
[] { return "a"; }},
|
||||||
|
// wrapper should ask for remaining data now
|
||||||
|
{"200 ok",
|
||||||
|
"content-length: 1\r\n"
|
||||||
|
"content-range: bytes 1-1/2\r\n",
|
||||||
|
[] { return "b"; },
|
||||||
|
{"Range: bytes=1-"}},
|
||||||
|
});
|
||||||
|
auto ft = makeFileTransfer(0);
|
||||||
|
auto [result, data] = ft->download(fmt("http://[::1]:%d", port));
|
||||||
|
ASSERT_EQ(data->drain(), "ab");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileTransfer, doesntRetrySetupForever)
|
||||||
|
{
|
||||||
|
auto [port, srv] = serveHTTP({
|
||||||
|
{"429 try again later", "content-length: 0\r\n", [] { return ""; }},
|
||||||
|
});
|
||||||
|
auto ft = makeFileTransfer(0);
|
||||||
|
ASSERT_THROW(ft->download(fmt("http://[::1]:%d", port)), FileTransferError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(FileTransfer, doesntRetryTransferForever)
|
||||||
|
{
|
||||||
|
constexpr size_t LIMIT = 20;
|
||||||
|
ASSERT_LT(fileTransferSettings.tries, LIMIT); // just to keep test runtime low
|
||||||
|
std::vector<Reply> replies;
|
||||||
|
for (size_t i = 0; i < LIMIT; i++) {
|
||||||
|
replies.emplace_back(
|
||||||
|
"200 ok",
|
||||||
|
fmt("content-length: %1%\r\n"
|
||||||
|
"accept-ranges: bytes\r\n"
|
||||||
|
"content-range: bytes %2%-%3%/%3%\r\n",
|
||||||
|
LIMIT - i,
|
||||||
|
i,
|
||||||
|
LIMIT),
|
||||||
|
[] { return "a"; }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
auto [port, srv] = serveHTTP(replies);
|
||||||
|
auto ft = makeFileTransfer(0);
|
||||||
|
ASSERT_THROW(ft->download(fmt("http://[::1]:%d", port)).second->drain(), FileTransferError);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue