Added an fdbserver role to run unit tests directly without a cluster or test spec file, and added a unit test parameters concept for passing options into unit tests.

Updated p2p network test to use unit test parameters instead of the environment.
This commit is contained in:
Steve Atherton 2021-04-04 21:36:05 -07:00
parent 781255d2c2
commit eec119e0d0
6 changed files with 137 additions and 38 deletions

View File

@ -135,7 +135,7 @@ ACTOR Future<Void> testerServerCore(TesterInterface interf,
LocalityData locality);
enum test_location_t { TEST_HERE, TEST_ON_SERVERS, TEST_ON_TESTERS };
enum test_type_t { TEST_TYPE_FROM_FILE, TEST_TYPE_CONSISTENCY_CHECK };
enum test_type_t { TEST_TYPE_FROM_FILE, TEST_TYPE_CONSISTENCY_CHECK, TEST_TYPE_UNIT_TESTS };
ACTOR Future<Void> runTests(Reference<ClusterConnectionFile> connFile,
test_type_t whatToRun,

View File

@ -66,6 +66,7 @@
#include "flow/SystemMonitor.h"
#include "flow/TLSConfig.actor.h"
#include "flow/Tracing.h"
#include "flow/UnitTest.h"
#if defined(__linux__) || defined(__FreeBSD__)
#include <execinfo.h>
@ -88,7 +89,7 @@ enum {
OPT_CONNFILE, OPT_SEEDCONNFILE, OPT_SEEDCONNSTRING, OPT_ROLE, OPT_LISTEN, OPT_PUBLICADDR, OPT_DATAFOLDER, OPT_LOGFOLDER, OPT_PARENTPID, OPT_TRACER, OPT_NEWCONSOLE,
OPT_NOBOX, OPT_TESTFILE, OPT_RESTARTING, OPT_RESTORING, OPT_RANDOMSEED, OPT_KEY, OPT_MEMLIMIT, OPT_STORAGEMEMLIMIT, OPT_CACHEMEMLIMIT, OPT_MACHINEID,
OPT_DCID, OPT_MACHINE_CLASS, OPT_BUGGIFY, OPT_VERSION, OPT_BUILD_FLAGS, OPT_CRASHONERROR, OPT_HELP, OPT_NETWORKIMPL, OPT_NOBUFSTDOUT, OPT_BUFSTDOUTERR,
OPT_TRACECLOCK, OPT_NUMTESTERS, OPT_DEVHELP, OPT_ROLLSIZE, OPT_MAXLOGS, OPT_MAXLOGSSIZE, OPT_KNOB, OPT_TESTSERVERS, OPT_TEST_ON_SERVERS, OPT_METRICSCONNFILE,
OPT_TRACECLOCK, OPT_NUMTESTERS, OPT_DEVHELP, OPT_ROLLSIZE, OPT_MAXLOGS, OPT_MAXLOGSSIZE, OPT_KNOB, OPT_UNITTESTPARAM, OPT_TESTSERVERS, OPT_TEST_ON_SERVERS, OPT_METRICSCONNFILE,
OPT_METRICSPREFIX, OPT_LOGGROUP, OPT_LOCALITY, OPT_IO_TRUST_SECONDS, OPT_IO_TRUST_WARN_ONLY, OPT_FILESYSTEM, OPT_PROFILER_RSS_SIZE, OPT_KVFILE,
OPT_TRACE_FORMAT, OPT_WHITELIST_BINPATH, OPT_BLOB_CREDENTIAL_FILE
};
@ -162,6 +163,7 @@ CSimpleOpt::SOption g_rgOptions[] = {
{ OPT_HELP, "--help", SO_NONE },
{ OPT_DEVHELP, "--dev-help", SO_NONE },
{ OPT_KNOB, "--knob_", SO_REQ_SEP },
{ OPT_UNITTESTPARAM, "--test_", SO_REQ_SEP },
{ OPT_LOCALITY, "--locality_", SO_REQ_SEP },
{ OPT_TESTSERVERS, "--testservers", SO_REQ_SEP },
{ OPT_TEST_ON_SERVERS, "--testonservers", SO_NONE },
@ -622,16 +624,19 @@ static void printUsage(const char* name, bool devhelp) {
printOptionUsage("-h, -?, --help", "Display this help and exit.");
if (devhelp) {
printf(" --build_flags Print build information and exit.\n");
printOptionUsage("-r ROLE, --role ROLE",
" Server role (valid options are fdbd, test, multitest,"
" simulation, networktestclient, networktestserver, restore"
" consistencycheck, kvfileintegritycheck, kvfilegeneratesums). The default is `fdbd'.");
printOptionUsage(
"-r ROLE, --role ROLE",
" Server role (valid options are fdbd, test, multitest,"
" simulation, networktestclient, networktestserver, restore"
" consistencycheck, kvfileintegritycheck, kvfilegeneratesums, unittests). The default is `fdbd'.");
#ifdef _WIN32
printOptionUsage("-n, --newconsole", " Create a new console.");
printOptionUsage("-q, --no_dialog", " Disable error dialog on crash.");
printOptionUsage("--parentpid PID", " Specify a process after whose termination to exit.");
#endif
printOptionUsage("-f TESTFILE, --testfile", " Testfile to run, defaults to `tests/default.txt'.");
printOptionUsage("-f TESTFILE, --testfile",
" Testfile to run, defaults to `tests/default.txt'. If role is `unittests', specifies which "
"unit tests to run as a search prefix.");
printOptionUsage("-R, --restarting", " Restart a previous simulation that was cleanly shut down.");
printOptionUsage("-s SEED, --seed SEED", " Random seed.");
printOptionUsage("-k KEY, --key KEY", "Target key for search role.");
@ -651,6 +656,8 @@ static void printUsage(const char* name, bool devhelp) {
printOptionUsage("--num_testers NUM",
" A multitester will wait for NUM testers before starting"
" (defaults to 1).");
printOptionUsage("--test_PARAMNAME PARAMVALUE",
" Set a UnitTest named parameter to the given value. Names are case sensitive.");
#ifdef __linux__
printOptionUsage("--rsssize SIZE",
" Turns on automatic heap profiling when RSS memory size exceeds"
@ -922,6 +929,7 @@ enum class ServerRole {
SkipListTest,
Test,
VersionedMapTest,
UnitTests
};
struct CLIOptions {
std::string commandLine;
@ -1044,6 +1052,15 @@ private:
knobs.push_back(std::make_pair(syn, args.OptionArg()));
break;
}
case OPT_UNITTESTPARAM: {
std::string syn = args.OptionSyntax();
if (!StringRef(syn).startsWith(LiteralStringRef("--test_"))) {
fprintf(stderr, "ERROR: unable to parse knob option '%s'\n", syn.c_str());
flushAndExit(FDB_EXIT_ERROR);
}
UnitTestCollection::setParam(syn.substr(7), args.OptionArg());
break;
}
case OPT_LOCALITY: {
std::string syn = args.OptionSyntax();
if (!StringRef(syn).startsWith(LiteralStringRef("--locality_"))) {
@ -1102,6 +1119,8 @@ private:
role = ServerRole::KVFileGenerateIOLogChecksums;
else if (!strcmp(sRole, "consistencycheck"))
role = ServerRole::ConsistencyCheck;
else if (!strcmp(sRole, "unittests"))
role = ServerRole::UnitTests;
else {
fprintf(stderr, "ERROR: Unknown role `%s'\n", sRole);
printHelpTeaser(argv[0]);
@ -1461,7 +1480,8 @@ private:
return StringRef(addr).startsWith(LiteralStringRef("auto:"));
});
if ((role != ServerRole::Simulation && role != ServerRole::CreateTemplateDatabase &&
role != ServerRole::KVFileIntegrityCheck && role != ServerRole::KVFileGenerateIOLogChecksums) ||
role != ServerRole::KVFileIntegrityCheck && role != ServerRole::KVFileGenerateIOLogChecksums &&
role != ServerRole::UnitTests) ||
autoPublicAddress) {
if (seedSpecified && !fileExists(connFile)) {
@ -1994,6 +2014,12 @@ int main(int argc, char* argv[]) {
StringRef(),
opts.localities));
g_network->run();
} else if (role == ServerRole::UnitTests) {
setupRunLoopProfiler();
auto m = startSystemMonitor(opts.dataFolder, opts.dcId, opts.zoneId, opts.zoneId);
f = stopAfter(runTests(
opts.connectionFile, TEST_TYPE_UNIT_TESTS, TEST_HERE, 1, opts.testFile, StringRef(), opts.localities));
g_network->run();
} else if (role == ServerRole::CreateTemplateDatabase) {
createTemplateDatabase();
} else if (role == ServerRole::NetworkTestClient) {

View File

@ -517,13 +517,6 @@ struct P2PNetworkTest {
self->listeners.size(),
self->remotes.size(),
self->connectionsOut);
printf("Request size: %s\n", self->requestBytes.toString().c_str());
printf("Response size: %s\n", self->replyBytes.toString().c_str());
printf("Requests per outgoing session: %d\n", self->requests.toString().c_str());
printf("Delay before socket read: %s\n", self->waitReadMilliseconds.toString().c_str());
printf("Delay before socket write: %s\n", self->waitWriteMilliseconds.toString().c_str());
printf("Delay before session close: %s\n", self->idleMilliseconds.toString().c_str());
printf("Send/Recv size %d bytes\n", FLOW_KNOBS->MAX_PACKET_SEND_BYTES);
for (auto n : self->remotes) {
printf("Remote: %s\n", n.toString().c_str());
@ -534,6 +527,19 @@ struct P2PNetworkTest {
actors.add(incoming(self, el));
}
printf("Request size: %s\n", self->requestBytes.toString().c_str());
printf("Response size: %s\n", self->replyBytes.toString().c_str());
printf("Requests per outgoing session: %s\n", self->requests.toString().c_str());
printf("Delay before socket read: %s\n", self->waitReadMilliseconds.toString().c_str());
printf("Delay before socket write: %s\n", self->waitWriteMilliseconds.toString().c_str());
printf("Delay before session close: %s\n", self->idleMilliseconds.toString().c_str());
printf("Send/Recv size %d bytes\n", FLOW_KNOBS->MAX_PACKET_SEND_BYTES);
if ((self->remotes.empty() || self->connectionsOut == 0) && self->listeners.empty()) {
printf("No listeners and no remotes or connectionsOut, so there is nothing to do!\n");
ASSERT((!self->remotes.empty() && (self->connectionsOut > 0)) || !self->listeners.empty());
}
if (!self->remotes.empty()) {
for (int i = 0; i < self->connectionsOut; ++i) {
actors.add(outgoing(self));
@ -549,27 +555,30 @@ struct P2PNetworkTest {
Future<Void> run() { return run_impl(this); }
};
int getEnvInt(const char* name, int defaultValue = 0) {
const char* val = getenv(name);
return val != nullptr ? atol(val) : defaultValue;
}
std::string getEnvStr(const char* name, std::string defaultValue = "") {
const char* val = getenv(name);
return val != nullptr ? val : defaultValue;
}
// TODO: Remove this hacky thing and make a "networkp2ptest" role in fdbserver
// Peer-to-Peer network test.
// One or more instances can be run and set to talk to each other.
// Each instance
// - listens on 0 or more listenerAddresses
// - maintains 0 or more connectionsOut at a time, each to a random choice from remoteAddresses
// Address lists are a string of comma-separated IP:port[:tls] strings.
//
// The other arguments can be specified as "fixedValue" or "minValue:maxValue".
// Each outgoing connection will live for a random requests count.
// Each request will
// - send a random requestBytes sized message
// - wait for a random replyBytes sized response.
// The client will close the connection after a random idleMilliseconds.
// Reads and writes can optionally preceded by random delays, waitReadMilliseconds and waitWriteMilliseconds.
TEST_CASE("!p2ptest") {
state P2PNetworkTest p2p(getEnvStr("listenerAddresses", ""),
getEnvStr("remoteAddresses", ""),
getEnvInt("connectionsOut", 0),
getEnvStr("requestBytes", "0"),
getEnvStr("replyBytes", "0"),
getEnvStr("requests", "0"),
getEnvStr("idleMilliseconds", "0"),
getEnvStr("waitReadMilliseconds", "0"),
getEnvStr("waitWriteMilliseconds", "0"));
state P2PNetworkTest p2p(UnitTestCollection::getParam("listenerAddresses").orDefault(""),
UnitTestCollection::getParam("remoteAddresses").orDefault(""),
UnitTestCollection::getIntParam("connectionsOut").orDefault(1),
UnitTestCollection::getParam("requestBytes").orDefault("50:100"),
UnitTestCollection::getParam("replyBytes").orDefault("500:1000"),
UnitTestCollection::getParam("requests").orDefault("10:10000"),
UnitTestCollection::getParam("idleMilliseconds").orDefault("0"),
UnitTestCollection::getParam("waitReadMilliseconds").orDefault("0"),
UnitTestCollection::getParam("waitWriteMilliseconds").orDefault("0"));
wait(p2p.run());
return Void();

View File

@ -763,7 +763,7 @@ ACTOR Future<DistributedTestResults> runWorkload(Database cx, std::vector<Tester
req.title = spec.title;
req.useDatabase = spec.useDB;
req.timeout = spec.timeout;
req.databasePingDelay = spec.databasePingDelay;
req.databasePingDelay = spec.useDB ? spec.databasePingDelay : 0.0;
req.options = spec.options;
req.clientId = i;
req.clientCount = testers.size();
@ -1577,8 +1577,10 @@ ACTOR Future<Void> runTests(Reference<ClusterConnectionFile> connFile,
auto cc = makeReference<AsyncVar<Optional<ClusterControllerFullInterface>>>();
auto ci = makeReference<AsyncVar<Optional<ClusterInterface>>>();
vector<Future<Void>> actors;
actors.push_back(reportErrors(monitorLeader(connFile, cc), "MonitorLeader"));
actors.push_back(reportErrors(extractClusterInterface(cc, ci), "ExtractClusterInterface"));
if (connFile) {
actors.push_back(reportErrors(monitorLeader(connFile, cc), "MonitorLeader"));
actors.push_back(reportErrors(extractClusterInterface(cc, ci), "ExtractClusterInterface"));
}
if (whatToRun == TEST_TYPE_CONSISTENCY_CHECK) {
TestSpec spec;
@ -1603,6 +1605,18 @@ ACTOR Future<Void> runTests(Reference<ClusterConnectionFile> connFile,
KeyValueRef(LiteralStringRef("shuffleShards"), LiteralStringRef("true")));
spec.options.push_back_deep(spec.options.arena(), options);
testSpecs.push_back(spec);
} else if (whatToRun == TEST_TYPE_UNIT_TESTS) {
TestSpec spec;
Standalone<VectorRef<KeyValueRef>> options;
spec.title = LiteralStringRef("UnitTests");
spec.startDelay = 0;
spec.useDB = false;
spec.timeout = 0;
options.push_back_deep(options.arena(),
KeyValueRef(LiteralStringRef("testName"), LiteralStringRef("UnitTests")));
options.push_back_deep(options.arena(), KeyValueRef(LiteralStringRef("testsMatching"), fileName));
spec.options.push_back_deep(spec.options.arena(), options);
testSpecs.push_back(spec);
} else {
ifstream ifs;
ifs.open(fileName.c_str(), ifstream::in);

View File

@ -26,3 +26,33 @@ UnitTest::UnitTest(const char* name, const char* file, int line, TestFunction fu
: name(name), file(file), line(line), func(func), next(g_unittests.tests) {
g_unittests.tests = this;
}
UnitTestParameters& UnitTestCollection::params() {
static UnitTestParameters p;
return p;
}
void UnitTestCollection::setParam(const std::string& name, const std::string& value) {
printf("setting %s = %s\n", name.c_str(), value.c_str());
params()[name] = value;
}
Optional<std::string> UnitTestCollection::getParam(const std::string& name) {
auto it = params().find(name);
if (it != params().end()) {
return it->second;
}
return {};
}
void UnitTestCollection::setParam(const std::string& name, int64_t value) {
setParam(name, format("%" PRId64, value));
};
Optional<int64_t> UnitTestCollection::getIntParam(const std::string& name) {
auto opt = getParam(name);
if (opt.present()) {
return atoll(opt.get().c_str());
}
return {};
}

View File

@ -45,6 +45,9 @@
#include "flow/flow.h"
#include <cinttypes>
// Unit test definition structured as a linked list item
struct UnitTest {
typedef Future<Void> (*TestFunction)();
@ -57,8 +60,25 @@ struct UnitTest {
UnitTest(const char* name, const char* file, int line, TestFunction func);
};
// Collection of unit tests in the form of a linked list
typedef std::map<std::string, std::string> UnitTestParameters;
struct UnitTestCollection {
UnitTest* tests;
// Map of named case-sensitive parameters available for all unit tests
static UnitTestParameters& params();
// Set a named parameter to a string value, replacing any existing value
static void setParam(const std::string& name, const std::string& value);
// Set a named parameter to an integer converted to a string value, replacing any existing value
static void setParam(const std::string& name, int64_t value);
// Get a parameter's value, will return !present() if parameter was not set
static Optional<std::string> getParam(const std::string& name);
// Get a parameter's value as an integer, will return !present() if parameter was not set
static Optional<int64_t> getIntParam(const std::string& name);
};
extern UnitTestCollection g_unittests;