sapling/eden/fs/utils/test/UnixSocketTest.cpp
Xavier Deguillard 4038c01e9f utils: remove //eden/fs/utils/test:linux_test
Summary:
The skip_on_mode_mac argument to cpp_unittest doesn't exist on Windows, and to
be consistent with the rest of the code, we can simply ifdef the code that
either doesn't compile, or doesn't run.

Note: For some reason the accessIncrementsAccessCount test fails when running
on Windows, but only if running after accessAddsProcessToProcessNameCache...

Reviewed By: chadaustin

Differential Revision: D24496450

fbshipit-source-id: fe18fe1d791a27fbe4bd03bd3e8c811feeb23f5f
2020-11-16 08:29:04 -08:00

307 lines
10 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
#ifndef _WIN32
#include "eden/fs/utils/UnixSocket.h"
#include "eden/fs/utils/FutureUnixSocket.h"
#include <folly/Exception.h>
#include <folly/File.h>
#include <folly/Random.h>
#include <folly/Range.h>
#include <folly/String.h>
#include <folly/experimental/TestUtil.h>
#include <folly/futures/Future.h>
#include <folly/io/IOBuf.h>
#include <folly/io/async/EventBase.h>
#include <folly/logging/xlog.h>
#include <folly/test/TestUtils.h>
#include <gtest/gtest.h>
#include <optional>
#include <random>
#include "eden/fs/testharness/TempFile.h"
using folly::ByteRange;
using folly::checkUnixError;
using folly::errnoStr;
using folly::EventBase;
using folly::File;
using folly::IOBuf;
using folly::StringPiece;
using std::make_unique;
using namespace std::chrono_literals;
using namespace facebook::eden;
namespace {
std::pair<folly::File, folly::File> createSocketPair() {
std::array<int, 2> sockets;
int rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets.data());
checkUnixError(rc, "socketpair failed");
return std::make_pair(
folly::File{sockets[0], true}, folly::File{sockets[1], true});
}
} // namespace
TEST(UnixSocket, getRemoteUID) {
auto sockets = createSocketPair();
EventBase evb;
auto socket1 = make_unique<FutureUnixSocket>(&evb, std::move(sockets.first));
auto socket2 = make_unique<FutureUnixSocket>(&evb, std::move(sockets.second));
EXPECT_EQ(getuid(), socket1->getRemoteUID());
EXPECT_EQ(getuid(), socket2->getRemoteUID());
}
struct DataSize {
explicit DataSize(size_t total, size_t maxChunk = 0)
: totalSize{total}, maxChunkSize{maxChunk} {}
size_t totalSize{0};
size_t maxChunkSize{0};
};
void testSendDataAndFiles(DataSize dataSize, size_t numFiles) {
XLOG(INFO) << "sending " << dataSize.totalSize << " bytes, " << numFiles
<< " files, with max chunk size of " << dataSize.maxChunkSize;
auto sockets = createSocketPair();
EventBase evb;
auto socket1 = make_unique<FutureUnixSocket>(&evb, std::move(sockets.first));
auto socket2 = make_unique<FutureUnixSocket>(&evb, std::move(sockets.second));
// Set a fairly large send and receive timeout for this test.
// On Mac OS X the send can take a fairly long-ish time when sending
// more than 1MB or so.
constexpr auto timeout = 10s;
socket1->setSendTimeout(timeout);
auto tmpFile = makeTempFile();
struct stat tmpFileStat;
if (fstat(tmpFile.fd(), &tmpFileStat) != 0) {
ADD_FAILURE() << "fstat failed: " << errnoStr(errno);
}
std::unique_ptr<IOBuf> sendBuf;
if (dataSize.maxChunkSize == 0) {
// Send everything in one chunk
sendBuf = IOBuf::create(dataSize.totalSize);
memset(sendBuf->writableTail(), 'a', dataSize.totalSize);
sendBuf->append(dataSize.totalSize);
} else {
// Use a fixed seed so we get repeatable results across unit test runs.
std::mt19937 rng;
rng.seed(1);
// Break the data into randomly sized chunks, from 0 to maxChunkSize bytes
uint8_t byteValue = 1;
size_t bytesLeft = dataSize.totalSize;
while (bytesLeft > 0) {
auto chunkSize = folly::Random::rand32(dataSize.maxChunkSize, rng);
if (chunkSize > bytesLeft) {
chunkSize = bytesLeft;
}
// Request a minimum of 32 bytes just to ensure we allocate some data
// rather than a null buffer if chunkSize is 0. This shouldn't really
// matter in practice, though.
auto buf = IOBuf::create(std::max(chunkSize, 32U));
memset(buf->writableTail(), byteValue, chunkSize);
buf->append(chunkSize);
bytesLeft -= chunkSize;
++byteValue; // Fill each chunk with a different byte value
if (sendBuf == nullptr) {
sendBuf = std::move(buf);
} else {
// Yes, unfortunately "prependChain()" is the method that appends this
// buffer to the end of the IOBuf chain. (The chain is a circularly
// linked list, prepending immediately in front of the head effectively
// appends to the end.) I'm unfortunately to blame for this horrible
// naming choice.
sendBuf->prependChain(std::move(buf));
}
}
}
std::vector<File> files;
for (size_t n = 0; n < numFiles; ++n) {
files.emplace_back(tmpFile.fd(), /* ownsFd */ false);
}
socket1->send(UnixSocket::Message(sendBuf->cloneAsValue(), std::move(files)))
.thenValue([](auto&&) { XLOG(DBG3) << "send complete"; })
.thenError([](const folly::exception_wrapper& ew) {
ADD_FAILURE() << "send error: " << ew.what();
});
std::optional<UnixSocket::Message> receivedMessage;
socket2->receive(timeout)
.thenValue([&receivedMessage](UnixSocket::Message&& msg) {
receivedMessage = std::move(msg);
})
.thenError([](const folly::exception_wrapper& ew) {
ADD_FAILURE() << "receive error: " << ew.what();
})
.ensure([&]() { evb.terminateLoopSoon(); });
evb.loopForever();
if (!receivedMessage) {
ADD_FAILURE() << "no message received";
return;
}
auto& msg = receivedMessage.value();
EXPECT_EQ(dataSize.totalSize, msg.data.computeChainDataLength());
EXPECT_EQ(StringPiece{sendBuf->coalesce()}, StringPiece{msg.data.coalesce()});
EXPECT_EQ(numFiles, msg.files.size());
for (size_t n = 0; n < numFiles; ++n) {
// The received file should be a different FD number than the one we
// sent but should refer to the same underlying file.
EXPECT_NE(tmpFile.fd(), msg.files[n].fd());
struct stat receivedFileStat;
if (fstat(msg.files[n].fd(), &receivedFileStat) != 0) {
ADD_FAILURE() << "fstat failed: " << errnoStr(errno);
}
EXPECT_EQ(tmpFileStat.st_dev, receivedFileStat.st_dev);
EXPECT_EQ(tmpFileStat.st_ino, receivedFileStat.st_ino);
}
}
TEST(UnixSocket, sendDataAndFiles) {
// Test various combinations of data length and number of files
testSendDataAndFiles(DataSize(5), 800);
testSendDataAndFiles(DataSize(0), 800);
testSendDataAndFiles(DataSize(5), 0);
testSendDataAndFiles(DataSize(0), 0);
testSendDataAndFiles(DataSize(4 * 1024 * 1024), 0);
testSendDataAndFiles(DataSize(4 * 1024 * 1024), 800);
testSendDataAndFiles(DataSize(32 * 1024 * 1024), 0);
testSendDataAndFiles(DataSize(32 * 1024 * 1024), 800);
// Send several MB of data split up into chunks of at most 1000 bytes.
// This will result in a lot of iovecs to send.
testSendDataAndFiles(DataSize(4 * 1024 * 1024, 1000), 800);
testSendDataAndFiles(DataSize(32 * 1024 * 1024, 1000), 0);
}
TEST(FutureUnixSocket, receiveQueue) {
auto sockets = createSocketPair();
EventBase evb;
auto socket1 = make_unique<FutureUnixSocket>(&evb, std::move(sockets.first));
auto socket2 = make_unique<FutureUnixSocket>(&evb, std::move(sockets.second));
std::vector<std::string> sendMessages = {
"hello world",
"test",
"message 3",
"",
"stuff",
"things",
"foobar",
};
// Call receive multiple times on socket2
std::vector<std::pair<int, UnixSocket::Message>> receivedMessages;
for (size_t n = 0; n < sendMessages.size(); ++n) {
auto future =
socket2->receive(500ms)
.thenValue([n, &receivedMessages](UnixSocket::Message&& msg) {
receivedMessages.emplace_back(n, std::move(msg));
})
.thenError([n, &evb](const folly::exception_wrapper& ew) {
ADD_FAILURE() << "receive " << n << " error: " << ew.what();
evb.terminateLoopSoon();
});
// Terminate the event loop after the final receive
if (n == sendMessages.size() - 1) {
std::move(future).ensure([&evb]() { evb.terminateLoopSoon(); });
}
}
// Now send the messages
for (const auto& msg : sendMessages) {
auto sendBuf = IOBuf(IOBuf::WRAP_BUFFER, ByteRange{StringPiece{msg}});
socket1->send(std::move(sendBuf))
.thenValue([](auto&&) { XLOG(DBG3) << "send complete"; })
.thenError([](const folly::exception_wrapper& ew) {
ADD_FAILURE() << "send error: " << ew.what();
});
}
evb.loopForever();
for (size_t n = 0; n < sendMessages.size(); ++n) {
if (n >= receivedMessages.size()) {
ADD_FAILURE() << "missing message " << n << " from receive queue";
continue;
}
EXPECT_EQ(n, receivedMessages[n].first);
EXPECT_EQ(
StringPiece{sendMessages[n]},
StringPiece{receivedMessages[n].second.data.coalesce()});
}
EXPECT_EQ(sendMessages.size(), receivedMessages.size());
}
TEST(FutureUnixSocket, attachEventBase) {
// A helper function to attach sockets to an EventBase, send a message, then
// detach from the EventBase
auto test = [&](EventBase* evb, FutureUnixSocket& s1, FutureUnixSocket& s2) {
s1.attachEventBase(evb);
s2.attachEventBase(evb);
SCOPE_EXIT {
s1.detachEventBase();
s2.detachEventBase();
};
const std::string msgData(100, 'a');
s1.send(UnixSocket::Message(IOBuf(IOBuf::COPY_BUFFER, msgData)))
.thenValue([](auto&&) { XLOG(DBG3) << "send complete"; })
.thenError([](const folly::exception_wrapper& ew) {
ADD_FAILURE() << "send error: " << ew.what();
});
std::optional<UnixSocket::Message> receivedMessage;
s2.receive(500ms)
.thenValue([&receivedMessage](UnixSocket::Message&& msg) {
receivedMessage = std::move(msg);
})
.thenError([](const folly::exception_wrapper& ew) {
ADD_FAILURE() << "receive error: " << ew.what();
})
.ensure([&]() { evb->terminateLoopSoon(); });
evb->loopForever();
EXPECT_TRUE(receivedMessage.has_value());
EXPECT_EQ(msgData, receivedMessage->data.moveToFbString().toStdString());
};
// Create two sockets that are initially not attached to an EventBase
auto sockets = createSocketPair();
auto socket1 = FutureUnixSocket(nullptr, std::move(sockets.first));
auto socket2 = FutureUnixSocket(nullptr, std::move(sockets.second));
// Test on one EventBase
{
EventBase evb1;
test(&evb1, socket1, socket2);
}
// Now test using another EventBase
{
EventBase evb2;
test(&evb2, socket2, socket1);
}
}
#endif