aboutsummaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
authoreldritch horrors <pennae@lix.systems>2024-04-28 02:04:08 +0200
committereldritch horrors <pennae@lix.systems>2024-05-10 02:21:11 +0200
commitb66451ae7fe12500f135137155c2aae6c406b2da (patch)
tree8e4200ddf3520e60e7b66a9a068c2379cce2ecc0 /tests/unit
parent28a98d152c071ec48b8bf510f87ac9710c56d7fb (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.cc123
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);
+}
}