diff options
author | eldritch horrors <pennae@lix.systems> | 2024-04-28 02:04:08 +0200 |
---|---|---|
committer | eldritch horrors <pennae@lix.systems> | 2024-05-10 02:21:11 +0200 |
commit | b66451ae7fe12500f135137155c2aae6c406b2da (patch) | |
tree | 8e4200ddf3520e60e7b66a9a068c2379cce2ecc0 /tests/unit | |
parent | 28a98d152c071ec48b8bf510f87ac9710c56d7fb (diff) |
libstore: de-callback-ify FileTransfer
also add a few more tests for exception propagation behavior. using
packaged_tasks and futures (which only allow a single call to a few
of their methods) introduces error paths that weren't there before.
Change-Id: I42ca5236f156fefec17df972f6e9be45989cf805
Diffstat (limited to 'tests/unit')
-rw-r--r-- | tests/unit/libstore/filetransfer.cc | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/tests/unit/libstore/filetransfer.cc b/tests/unit/libstore/filetransfer.cc index 192bf81ef..ebd38f19d 100644 --- a/tests/unit/libstore/filetransfer.cc +++ b/tests/unit/libstore/filetransfer.cc @@ -1,12 +1,114 @@ #include "filetransfer.hh" +#include <cstdint> +#include <exception> #include <future> #include <gtest/gtest.h> +#include <netinet/in.h> +#include <stdexcept> +#include <string_view> +#include <sys/poll.h> +#include <sys/socket.h> +#include <thread> +#include <unistd.h> + +// local server tests don't work on darwin without some incantations +// the horrors do not want to look up. contributions welcome though! +#if __APPLE__ +#define NOT_ON_DARWIN(n) DISABLED_##n +#else +#define NOT_ON_DARWIN(n) n +#endif using namespace std::chrono_literals; namespace nix { +static std::tuple<uint16_t, AutoCloseFD> +serveHTTP(std::string_view status, std::string_view headers, std::function<std::string_view()> content) +{ + AutoCloseFD listener(::socket(AF_INET6, SOCK_STREAM, 0)); + if (!listener) { + throw SysError(errno, "socket() failed"); + } + + Pipe trigger; + trigger.create(); + + sockaddr_in6 addr = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_LOOPBACK_INIT, + }; + socklen_t len = sizeof(addr); + if (::bind(listener.get(), reinterpret_cast<const sockaddr *>(&addr), sizeof(addr)) < 0) { + throw SysError(errno, "bind() failed"); + } + if (::getsockname(listener.get(), reinterpret_cast<sockaddr *>(&addr), &len) < 0) { + throw SysError(errno, "getsockname() failed"); + } + if (::listen(listener.get(), 1) < 0) { + throw SysError(errno, "listen() failed"); + } + + std::thread( + [status, headers, content](AutoCloseFD socket, AutoCloseFD trigger) { + while (true) { + pollfd pfds[2] = { + { + .fd = socket.get(), + .events = POLLIN, + }, + { + .fd = trigger.get(), + .events = POLLHUP, + }, + }; + + if (::poll(pfds, 2, -1) <= 0) { + throw SysError(errno, "poll() failed"); + } + if (pfds[1].revents & POLLHUP) { + return; + } + if (!(pfds[0].revents & POLLIN)) { + continue; + } + + AutoCloseFD conn(::accept(socket.get(), nullptr, nullptr)); + if (!conn) { + throw SysError(errno, "accept() failed"); + } + + auto send = [&](std::string_view bit) { + while (!bit.empty()) { + auto written = ::write(conn.get(), bit.data(), bit.size()); + if (written < 0) { + throw SysError(errno, "write() failed"); + } + bit.remove_prefix(written); + } + }; + + send("HTTP/1.1 "); + send(status); + send("\r\n"); + send(headers); + send("\r\n"); + send(content()); + ::shutdown(conn.get(), SHUT_RDWR); + } + }, + std::move(listener), + std::move(trigger.readSide) + ) + .detach(); + + return { + ntohs(addr.sin6_port), + std::move(trigger.writeSide), + }; +} + TEST(FileTransfer, exceptionAbortsDownload) { struct Done @@ -29,4 +131,25 @@ TEST(FileTransfer, exceptionAbortsDownload) (void) new auto(std::move(reset)); } } + +TEST(FileTransfer, NOT_ON_DARWIN(reportsSetupErrors)) +{ + auto [port, srv] = serveHTTP("404 not found", "", [] { return ""; }); + auto ft = makeFileTransfer(); + ASSERT_THROW( + ft->download(FileTransferRequest(fmt("http://[::1]:%d/index", port))), + FileTransferError); +} + +TEST(FileTransfer, NOT_ON_DARWIN(reportsTransferError)) +{ + auto [port, srv] = serveHTTP("200 ok", "content-length: 100\r\n", [] { + std::this_thread::sleep_for(10ms); + return ""; + }); + auto ft = makeFileTransfer(); + FileTransferRequest req(fmt("http://[::1]:%d/index", port)); + req.baseRetryTimeMs = 0; + ASSERT_THROW(ft->download(req), FileTransferError); +} } |