1164 lines
42 KiB
C++
1164 lines
42 KiB
C++
/*
|
|
* FuzzApiCorrectness.actor.cpp
|
|
*
|
|
* This source file is part of the FoundationDB open source project
|
|
*
|
|
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "flow/actorcompiler.h"
|
|
#include "fdbserver/TesterInterface.h"
|
|
#include "fdbclient/ThreadSafeTransaction.h"
|
|
#include "flow/ActorCollection.h"
|
|
#include "workloads.h"
|
|
|
|
#include <limits.h>
|
|
#include <mutex>
|
|
#include <functional>
|
|
#include <sstream>
|
|
|
|
namespace ph = std::placeholders;
|
|
|
|
// This allows us to dictate which exceptions we SHOULD get.
|
|
// We can use this to suppress expected exceptions, and take action
|
|
// if we don't get an exception wqe should have gotten.
|
|
struct ExceptionContract {
|
|
enum occurance_t { Never = 0, Possible = 1, Always = 2 };
|
|
|
|
std::string func;
|
|
std::map<int, occurance_t> expected;
|
|
std::function<void (TraceEvent &)> augment;
|
|
|
|
ExceptionContract(const char *func_, const std::function<void (TraceEvent &)> &augment_) : func(func_), augment(augment_) {}
|
|
ExceptionContract &operator=(const std::map<int, occurance_t> &e) {
|
|
expected = e;
|
|
return *this;
|
|
}
|
|
|
|
static occurance_t possibleButRequiredIf(bool in) { return in ? Always : Possible; }
|
|
static occurance_t requiredIf(bool in) { return in ? Always : Never; }
|
|
static occurance_t possibleIf(bool in) { return in ? Possible : Never; }
|
|
|
|
void handleException(const Error &e) const {
|
|
// We should always ignore these.
|
|
if (e.code() == error_code_used_during_commit ||
|
|
e.code() == error_code_transaction_too_old ||
|
|
e.code() == error_code_future_version ||
|
|
e.code() == error_code_transaction_cancelled ||
|
|
e.code() == error_code_key_too_large ||
|
|
e.code() == error_code_value_too_large)
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto i = expected.find(e.code());
|
|
if (i != expected.end() && i->second != Never)
|
|
{
|
|
Severity s = (i->second == Possible) ? SevWarn : SevInfo;
|
|
TraceEvent evt(s, func.c_str());
|
|
evt.error(e).detail("Thrown", true)
|
|
.detail("Expected", i->second == Possible ? "possible" : "always").backtrace();
|
|
if (augment)
|
|
augment(evt);
|
|
return;
|
|
}
|
|
|
|
TraceEvent evt(SevError, func.c_str());
|
|
evt.error(e).detail("Thrown", true).detail("Expected", "never").backtrace();
|
|
if (augment)
|
|
augment(evt);
|
|
throw e;
|
|
}
|
|
|
|
// Return true if we should have thrown, but didn't.
|
|
void handleNotThrown() const {
|
|
for (auto i : expected) {
|
|
if (i.second == Always) {
|
|
TraceEvent evt(SevError, func.c_str());
|
|
evt.error(Error::fromUnvalidatedCode(i.first)).detail("Thrown", false).detail("Expected", "always").backtrace();
|
|
if (augment)
|
|
augment(evt);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
struct FuzzApiCorrectnessWorkload : TestWorkload {
|
|
static std::once_flag onceFlag;
|
|
static std::vector<std::function<Future<Void> (unsigned int const &, FuzzApiCorrectnessWorkload * const &, Reference<ITransaction> const &)>> testCases;
|
|
|
|
double testDuration;
|
|
int numOps;
|
|
bool rarelyCommit, adjacentKeys;
|
|
int minNode, nodes;
|
|
std::pair<int,int> valueSizeRange;
|
|
int maxClearSize;
|
|
double initialKeyDensity;
|
|
bool useSystemKeys;
|
|
std::string keyPrefix;
|
|
KeyRange conflictRange;
|
|
unsigned int operationId;
|
|
int64_t maximumTotalData;
|
|
|
|
bool success;
|
|
|
|
FuzzApiCorrectnessWorkload(WorkloadContext const& wcx)
|
|
: TestWorkload(wcx), operationId(0), success(true) {
|
|
std::call_once(onceFlag, [&]() { addTestCases(); });
|
|
|
|
testDuration = getOption( options, LiteralStringRef("testDuration"), 60.0 );
|
|
numOps = getOption( options, LiteralStringRef("numOps"), 21 );
|
|
rarelyCommit = getOption( options, LiteralStringRef("rarelyCommit"), false );
|
|
maximumTotalData = getOption( options, LiteralStringRef("maximumTotalData"), 15e6);
|
|
minNode = getOption( options, LiteralStringRef("minNode"), 0);
|
|
adjacentKeys = g_random->coinflip();
|
|
useSystemKeys = g_random->coinflip();
|
|
initialKeyDensity = g_random->random01(); // This fraction of keys are present before the first transaction (and after an unknown result)
|
|
|
|
if( adjacentKeys ) {
|
|
nodes = std::min<int64_t>( g_random->randomInt(1, 4 << g_random->randomInt(0,14)), CLIENT_KNOBS->KEY_SIZE_LIMIT * 1.2 );
|
|
}
|
|
else {
|
|
nodes = g_random->randomInt(1, 4 << g_random->randomInt(0,20));
|
|
}
|
|
|
|
int newNodes = std::min<int>(nodes, maximumTotalData / (getKeyForIndex(nodes).size() + valueSizeRange.second));
|
|
minNode = std::max(minNode, nodes - newNodes);
|
|
nodes = newNodes;
|
|
|
|
if(useSystemKeys && g_random->coinflip()) {
|
|
keyPrefix = "\xff\x01";
|
|
}
|
|
|
|
maxClearSize = 1<<g_random->randomInt(0, 20);
|
|
conflictRange = KeyRangeRef( LiteralStringRef("\xfe"), LiteralStringRef("\xfe\x00") );
|
|
TraceEvent("FuzzApiCorrectnessConfiguration")
|
|
.detail("Nodes", nodes)
|
|
.detail("InitialKeyDensity", initialKeyDensity)
|
|
.detail("AdjacentKeys", adjacentKeys)
|
|
.detail("ValueSizeMin", valueSizeRange.first)
|
|
.detail("ValueSizeRange", valueSizeRange.second)
|
|
.detail("MaxClearSize", maxClearSize)
|
|
.detail("UseSystemKeys", useSystemKeys);
|
|
|
|
TraceEvent("RemapEventSeverity").detail("TargetEvent", "Net2_LargePacket").detail("OriginalSeverity", SevWarnAlways).detail("NewSeverity", SevInfo);
|
|
TraceEvent("RemapEventSeverity").detail("TargetEvent", "LargeTransaction").detail("OriginalSeverity", SevWarnAlways).detail("NewSeverity", SevInfo);
|
|
}
|
|
|
|
virtual std::string description() { return "FuzzApiCorrectness"; }
|
|
|
|
virtual Future<Void> start( Database const& cx ) {
|
|
if( clientId == 0 ) {
|
|
return loadAndRun( cx, this );
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
virtual Future<bool> check( Database const& cx ) {
|
|
return success;
|
|
}
|
|
|
|
Key getRandomKey() {
|
|
return getKeyForIndex( g_random->randomInt(0, nodes ) );
|
|
}
|
|
|
|
Key getKeyForIndex( int idx ) {
|
|
idx += minNode;
|
|
if( adjacentKeys ) {
|
|
return Key( keyPrefix + std::string( idx, '\x00' ) );
|
|
} else {
|
|
return Key( keyPrefix + format( "%010d", idx ) );
|
|
}
|
|
}
|
|
|
|
Value getRandomValue() {
|
|
return Value( std::string( g_random->randomInt(valueSizeRange.first,valueSizeRange.second+1), 'x' ) );
|
|
}
|
|
|
|
virtual void getMetrics( vector<PerfMetric>& m ) {
|
|
//m.push_back( transactions.getMetric() );
|
|
//m.push_back( retries.getMetric() );
|
|
}
|
|
|
|
ACTOR Future<Void> loadAndRun( Database db, FuzzApiCorrectnessWorkload* self ) {
|
|
state double startTime = now();
|
|
state Reference<IDatabase> cx = wait( unsafeThreadFutureToFuture( ThreadSafeDatabase::createFromExistingDatabase(db) ) );
|
|
try {
|
|
loop {
|
|
state int i = 0;
|
|
state int keysPerBatch = std::min<int64_t>(1000, 1 + CLIENT_KNOBS->TRANSACTION_SIZE_LIMIT / 2 / (self->getKeyForIndex(self->nodes).size() + self->valueSizeRange.second));
|
|
for(; i < self->nodes; i+=keysPerBatch ) {
|
|
state Reference<ITransaction> tr = cx->createTransaction();
|
|
loop {
|
|
if( now() - startTime > self->testDuration )
|
|
return Void();
|
|
try {
|
|
if( i == 0 ) {
|
|
tr->setOption( FDBTransactionOptions::ACCESS_SYSTEM_KEYS );
|
|
tr->addWriteConflictRange(allKeys); // To prevent a write only transaction whose commit was previously cancelled from being reordered after this transaction
|
|
tr->clear( normalKeys );
|
|
}
|
|
if( self->useSystemKeys )
|
|
tr->setOption( FDBTransactionOptions::ACCESS_SYSTEM_KEYS );
|
|
|
|
int end = std::min(self->nodes, i+keysPerBatch );
|
|
tr->clear( KeyRangeRef( self->getKeyForIndex(i), self->getKeyForIndex(end) ) );
|
|
|
|
for( int j = i; j < end; j++ ) {
|
|
if ( g_random->random01() < self->initialKeyDensity ) {
|
|
Key key = self->getKeyForIndex( j );
|
|
if( key.size() <= (key.startsWith(systemKeys.begin) ? CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT : CLIENT_KNOBS->KEY_SIZE_LIMIT)) {
|
|
Value value = self->getRandomValue();
|
|
value = value.substr(0,std::min<int>(value.size(),CLIENT_KNOBS->VALUE_SIZE_LIMIT));
|
|
tr->set( key, value );
|
|
}
|
|
}
|
|
}
|
|
Void _ = wait( unsafeThreadFutureToFuture( tr->commit() ) );
|
|
//TraceEvent("WDRInitBatch").detail("I", i).detail("CommittedVersion", tr->getCommittedVersion());
|
|
break;
|
|
} catch( Error &e ) {
|
|
Void _ = wait( unsafeThreadFutureToFuture( tr->onError( e ) ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
loop {
|
|
try {
|
|
Void _ = wait( self->randomTransaction( cx, self ) && delay( self->numOps * .001 ) );
|
|
} catch( Error &e ) {
|
|
if( e.code() != error_code_not_committed )
|
|
throw e;
|
|
break;
|
|
}
|
|
if( now() - startTime > self->testDuration )
|
|
return Void();
|
|
}
|
|
}
|
|
} catch (Error &e) {
|
|
TraceEvent("FuzzLoadAndRunError").error(e).backtrace();
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
ACTOR Future<Void> randomTransaction( Reference<IDatabase> cx, FuzzApiCorrectnessWorkload* self ) {
|
|
state Reference<ITransaction> tr = cx->createTransaction();
|
|
state bool readYourWritesDisabled = g_random->coinflip();
|
|
state bool readAheadDisabled = g_random->coinflip();
|
|
state std::vector<Future<Void>> operations;
|
|
state int waitLocation = 0;
|
|
|
|
loop {
|
|
state bool cancelled = false;
|
|
if( readYourWritesDisabled )
|
|
tr->setOption( FDBTransactionOptions::READ_YOUR_WRITES_DISABLE );
|
|
if( readAheadDisabled )
|
|
tr->setOption( FDBTransactionOptions::READ_AHEAD_DISABLE );
|
|
if( self->useSystemKeys ) {
|
|
tr->setOption( FDBTransactionOptions::ACCESS_SYSTEM_KEYS );
|
|
}
|
|
tr->addWriteConflictRange( self->conflictRange );
|
|
|
|
try {
|
|
state int numWaits = g_random->randomInt( 1, 5 );
|
|
state int i = 0;
|
|
|
|
// Code path here will create a bunch of ops, wait for ONE of them to complete,
|
|
// then add more ops, wait for another one, and so on.
|
|
for(; i < numWaits; i++ ) {
|
|
state int numOps = g_random->randomInt( 1, self->numOps );
|
|
state int j = 0;
|
|
for(; j < numOps; j++ ) {
|
|
state int operationType = g_random->randomInt(0, testCases.size());
|
|
printf("%d: Selected Operation %d\n", self->operationId+1, operationType);
|
|
try {
|
|
operations.push_back(testCases[operationType](++self->operationId, self, tr));
|
|
} catch( Error &e ) {
|
|
TraceEvent(SevWarn, "IgnoredOperation").error(e)
|
|
.detail("Operation", operationType).detail("Id", self->operationId);
|
|
}
|
|
}
|
|
|
|
// Wait for a random op to complete.
|
|
if( waitLocation < operations.size() ) {
|
|
int waitOp = g_random->randomInt(waitLocation,operations.size());
|
|
Void _ = wait( operations[waitOp] );
|
|
Void _ = wait( delay(0.000001) ); //to ensure errors have propgated from reads to commits
|
|
waitLocation = operations.size();
|
|
}
|
|
}
|
|
Void _ = wait( waitForAll( operations ) );
|
|
try {
|
|
Void _ = wait( timeoutError( unsafeThreadFutureToFuture( tr->commit() ), 30) );
|
|
}
|
|
catch( Error &e ) {
|
|
if(e.code() == error_code_client_invalid_operation ||
|
|
e.code() == error_code_transaction_too_large)
|
|
{
|
|
throw not_committed();
|
|
}
|
|
}
|
|
break;
|
|
} catch( Error &e ) {
|
|
operations.clear();
|
|
waitLocation = 0;
|
|
|
|
if( e.code() == error_code_not_committed ||
|
|
e.code() == error_code_commit_unknown_result ||
|
|
cancelled )
|
|
{
|
|
throw not_committed();
|
|
}
|
|
try {
|
|
Void _ = wait( unsafeThreadFutureToFuture( tr->onError(e) ) );
|
|
} catch( Error &e ) {
|
|
if( e.code() == error_code_transaction_timed_out ) {
|
|
throw not_committed();
|
|
}
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
template<typename Subclass, typename T>
|
|
struct BaseTest {
|
|
typedef T value_type;
|
|
|
|
ACTOR static Future<Void> runTest( unsigned int id, FuzzApiCorrectnessWorkload *wl, Reference<ITransaction> tr ) {
|
|
state Subclass self(id, wl);
|
|
|
|
try {
|
|
value_type result = wait( timeoutError( BaseTest::runTest2(tr, &self), 1000) );
|
|
self.contract.handleNotThrown();
|
|
return self.errorCheck(tr, result);
|
|
} catch (Error &e) {
|
|
self.contract.handleException(e);
|
|
}
|
|
return Void();
|
|
}
|
|
|
|
ACTOR static Future<value_type> runTest2( Reference<ITransaction> tr, Subclass *self ) {
|
|
state Future<value_type> future = unsafeThreadFutureToFuture( self->createFuture(tr) );
|
|
|
|
// Create may have added some other things to do before
|
|
// we can get the result.
|
|
state std::vector<ThreadFuture<Void> >::iterator i;
|
|
for (i = self->pre_steps.begin(); i != self->pre_steps.end(); ++i) {
|
|
Void _ = wait(unsafeThreadFutureToFuture( *i ));
|
|
}
|
|
|
|
value_type result = wait( future );
|
|
if (future.isError()) {
|
|
self->contract.handleException(future.getError());
|
|
} else {
|
|
ASSERT( future.isValid() );
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
virtual ThreadFuture<value_type> createFuture( Reference<ITransaction> tr ) = 0;
|
|
virtual Void errorCheck(Reference<ITransaction> tr, value_type result) { return Void(); }
|
|
virtual void augmentTrace(TraceEvent &e) const { e.detail("Id", id); }
|
|
|
|
protected:
|
|
unsigned int id;
|
|
FuzzApiCorrectnessWorkload *workload;
|
|
ExceptionContract contract;
|
|
std::vector<ThreadFuture<Void> > pre_steps;
|
|
|
|
BaseTest(unsigned int id_, FuzzApiCorrectnessWorkload *wl, const char *func)
|
|
: id(id_), workload(wl), contract(func, std::bind(&Subclass::augmentTrace, static_cast<Subclass *>(this), ph::_1)) {}
|
|
|
|
static Key makeKey() {
|
|
double ksrv = g_random->random01();
|
|
|
|
// 25% of the time it's empty, 25% it's above range.
|
|
if (ksrv < 0.25)
|
|
return Key();
|
|
|
|
int64_t key_size;
|
|
if (ksrv < 0.5)
|
|
key_size = g_random->randomInt64(1, CLIENT_KNOBS->KEY_SIZE_LIMIT + 1) + CLIENT_KNOBS->KEY_SIZE_LIMIT;
|
|
else
|
|
key_size = g_random->randomInt64(1, CLIENT_KNOBS->KEY_SIZE_LIMIT + 1);
|
|
|
|
std::string skey;
|
|
skey.reserve(key_size);
|
|
for (size_t j = 0; j < key_size; ++j)
|
|
skey.append(1, (char) g_random->randomInt(0, 256));
|
|
|
|
return Key(skey);
|
|
}
|
|
|
|
static Value makeValue() {
|
|
double vrv = g_random->random01();
|
|
|
|
// 25% of the time it's empty, 25% it's above range.
|
|
if (vrv < 0.25)
|
|
return Value();
|
|
|
|
int64_t value_size;
|
|
if (vrv < 0.5)
|
|
value_size = g_random->randomInt64(1, CLIENT_KNOBS->VALUE_SIZE_LIMIT + 1) + CLIENT_KNOBS->VALUE_SIZE_LIMIT;
|
|
else
|
|
value_size = g_random->randomInt64(1, CLIENT_KNOBS->VALUE_SIZE_LIMIT + 1);
|
|
|
|
std::string svalue;
|
|
svalue.reserve(value_size);
|
|
for (size_t j = 0; j < value_size; ++j)
|
|
svalue.append(1, (char) g_random->randomInt(0, 256));
|
|
|
|
return Value(svalue);
|
|
}
|
|
|
|
static KeySelector makeKeySel() {
|
|
// 40% of the time no offset, 30% it's a positive or negative.
|
|
double orv = g_random->random01();
|
|
int offs = 0;
|
|
if (orv >= 0.4) {
|
|
if (orv < 0.7)
|
|
offs = g_random->randomInt(INT_MIN, 0);
|
|
else
|
|
offs = g_random->randomInt(0, INT_MAX)+1;
|
|
}
|
|
return KeySelectorRef(makeKey(), g_random->coinflip(), offs);
|
|
}
|
|
|
|
static GetRangeLimits makeRangeLimits() {
|
|
double lrv = g_random->random01();
|
|
int rowlimit = 0;
|
|
if (lrv >= 0.2) {
|
|
if (lrv < 0.4)
|
|
rowlimit = g_random->randomInt(INT_MIN, 0);
|
|
else
|
|
rowlimit = g_random->randomInt(0, INT_MAX)+1;
|
|
}
|
|
|
|
if (g_random->coinflip())
|
|
return GetRangeLimits(rowlimit);
|
|
|
|
lrv = g_random->random01();
|
|
int bytelimit = 0;
|
|
if (lrv >= 0.2) {
|
|
if (lrv < 0.4)
|
|
bytelimit = g_random->randomInt(INT_MIN, 0);
|
|
else
|
|
bytelimit = g_random->randomInt(0, INT_MAX)+1;
|
|
}
|
|
|
|
// Try again if both are 0.
|
|
if (!rowlimit && !bytelimit)
|
|
return makeRangeLimits();
|
|
|
|
return GetRangeLimits(rowlimit, bytelimit);
|
|
}
|
|
|
|
static bool isOverlapping(const Key &begin, const Key &end, const KeyRangeRef &range) {
|
|
return ((range.begin >= begin && range.begin < end) ||
|
|
(range.end >= begin && range.end < end) ||
|
|
(begin >= range.begin && begin < range.end) ||
|
|
(end >= range.begin && end < range.end));
|
|
}
|
|
static bool isOverlapping(const Key &begin, const Key &end, const KeyRef &key) {
|
|
return (key >= begin && key < end);
|
|
}
|
|
|
|
static std::string slashToEnd(const KeyRef &key) {
|
|
return key.toString().replace(key.size()-1, 1, 1, '0');
|
|
}
|
|
|
|
static KeyRangeRef &getServerKeys() {
|
|
static std::string serverKeysEnd = slashToEnd(serverKeysPrefix);
|
|
static KeyRangeRef serverKeys(serverKeysPrefix, StringRef(serverKeysEnd));
|
|
return serverKeys;
|
|
}
|
|
|
|
static KeyRangeRef &getGlobalKeys() {
|
|
static std::string globalKeysPrefix2 = globalKeysPrefix.toString() + "/";
|
|
static std::string globalKeysEnd = slashToEnd(globalKeysPrefix2);
|
|
static KeyRangeRef globalKeys(globalKeysPrefix2, StringRef(globalKeysEnd));
|
|
return globalKeys;
|
|
}
|
|
|
|
static bool isProtectedKey(const Key &begin, const Key &end) {
|
|
if (end < begin)
|
|
return false;
|
|
|
|
return (isOverlapping(begin, end, keyServersKeys) ||
|
|
isOverlapping(begin, end, serverListKeys) ||
|
|
isOverlapping(begin, end, processClassKeys) ||
|
|
isOverlapping(begin, end, configKeys) ||
|
|
isOverlapping(begin, end, workerListKeys) ||
|
|
isOverlapping(begin, end, backupLogKeys) ||
|
|
isOverlapping(begin, end, getServerKeys()) ||
|
|
isOverlapping(begin, end, getGlobalKeys()) ||
|
|
isOverlapping(begin, end, coordinatorsKey) ||
|
|
isOverlapping(begin, end, backupEnabledKey) ||
|
|
isOverlapping(begin, end, backupVersionKey) );
|
|
}
|
|
|
|
static bool isProtectedKey(const Key &k) {
|
|
return (isOverlapping(keyServersKeys.begin, keyServersKeys.end, k) ||
|
|
isOverlapping(serverListKeys.begin, serverListKeys.end, k) ||
|
|
isOverlapping(processClassKeys.begin, processClassKeys.end, k) ||
|
|
isOverlapping(configKeys.begin, configKeys.end, k) ||
|
|
isOverlapping(workerListKeys.begin, workerListKeys.end, k) ||
|
|
isOverlapping(backupLogKeys.begin, backupLogKeys.end, k) ||
|
|
isOverlapping(getServerKeys().begin, getServerKeys().end, k) ||
|
|
isOverlapping(getGlobalKeys().begin, getGlobalKeys().end, k) ||
|
|
coordinatorsKey == k || backupEnabledKey == k || backupVersionKey == k);
|
|
}
|
|
};
|
|
|
|
template<typename Subclass>
|
|
struct BaseTestCallback : BaseTest<Subclass, Void> {
|
|
typedef typename BaseTest<Subclass, Void>::value_type value_type;
|
|
|
|
BaseTestCallback(unsigned int id, FuzzApiCorrectnessWorkload *wl, const char *func)
|
|
: BaseTest<Subclass, Void>(id, wl, func) {}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
callback(tr);
|
|
return tr.castTo<ThreadSafeTransaction>()->checkDeferredError();
|
|
}
|
|
|
|
Void errorCheck(Reference<ITransaction> tr, value_type result) {
|
|
callbackErrorCheck(tr);
|
|
return Void();
|
|
}
|
|
|
|
virtual void callback(Reference<ITransaction> tr) = 0;
|
|
virtual void callbackErrorCheck(Reference<ITransaction> tr) { }
|
|
};
|
|
|
|
struct TestSetVersion : public BaseTest<TestSetVersion, Version> {
|
|
typedef BaseTest<TestSetVersion, Version> base_type;
|
|
Version v;
|
|
|
|
TestSetVersion(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestSetVersion") {
|
|
if (g_random->coinflip())
|
|
v = g_random->randomInt64(INT64_MIN, 0);
|
|
else
|
|
v = g_random->randomInt64(0, INT64_MAX);
|
|
|
|
contract = {
|
|
std::make_pair( error_code_read_version_already_set, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_version_invalid, ExceptionContract::requiredIf(v <= 0) )
|
|
};
|
|
|
|
|
|
}
|
|
|
|
ThreadFuture<Version> createFuture(Reference<ITransaction> tr) {
|
|
tr->setVersion(v);
|
|
pre_steps.push_back(tr.castTo<ThreadSafeTransaction>()->checkDeferredError());
|
|
return tr->getReadVersion();
|
|
}
|
|
|
|
Void errorCheck(Reference<ITransaction> tr, value_type result) {
|
|
ASSERT(v == result);
|
|
return Void();
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Version", v);
|
|
}
|
|
};
|
|
|
|
struct TestGet : public BaseTest<TestGet, Optional<Value>> {
|
|
typedef BaseTest<TestGet, Optional<Value>> base_type;
|
|
Key key;
|
|
|
|
TestGet(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGet") {
|
|
key = makeKey();
|
|
contract = {
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf((key >= (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->get(key, g_random->coinflip());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key", printable(key));
|
|
}
|
|
};
|
|
|
|
struct TestGetKey : public BaseTest<TestGetKey, Key> {
|
|
typedef BaseTest<TestGetKey, Key> base_type;
|
|
KeySelector keysel;
|
|
|
|
TestGetKey(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGetKey") {
|
|
keysel = makeKeySel();
|
|
contract = {
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf((keysel.getKey() > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->getKey(keysel, g_random->coinflip());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("KeySel", keysel.toString());
|
|
}
|
|
};
|
|
|
|
struct TestGetRange0 : public BaseTest<TestGetRange0, Standalone<RangeResultRef>> {
|
|
typedef BaseTest<TestGetRange0, Standalone<RangeResultRef>> base_type;
|
|
KeySelector keysel1, keysel2;
|
|
int limit;
|
|
|
|
TestGetRange0(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGetRange0") {
|
|
keysel1 = makeKeySel();
|
|
keysel2 = makeKeySel();
|
|
limit = 0;
|
|
|
|
double lrv = g_random->random01();
|
|
if (lrv > 0.20) {
|
|
if (lrv < 0.4)
|
|
limit = g_random->randomInt(INT_MIN, 0);
|
|
else
|
|
limit = g_random->randomInt(0, INT_MAX)+1;
|
|
}
|
|
|
|
contract = {
|
|
std::make_pair( error_code_range_limits_invalid, ExceptionContract::possibleButRequiredIf(limit < 0) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(keysel1.getKey() > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(keysel2.getKey() > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->getRange(keysel1, keysel2, limit, g_random->coinflip(), g_random->coinflip());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("KeySel1", keysel1.toString()).detail("KeySel2", keysel2.toString()).detail("Limit", limit);
|
|
}
|
|
};
|
|
|
|
struct TestGetRange1 : public BaseTest<TestGetRange1, Standalone<RangeResultRef>> {
|
|
typedef BaseTest<TestGetRange1, Standalone<RangeResultRef>> base_type;
|
|
KeySelector keysel1, keysel2;
|
|
GetRangeLimits limits;
|
|
|
|
TestGetRange1(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGetRange1") {
|
|
keysel1 = makeKeySel();
|
|
keysel2 = makeKeySel();
|
|
limits = makeRangeLimits();
|
|
contract = {
|
|
std::make_pair( error_code_range_limits_invalid, ExceptionContract::possibleButRequiredIf( !limits.isReached() && !limits.isValid()) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(keysel1.getKey() > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(keysel2.getKey() > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->getRange(keysel1, keysel2, limits, g_random->coinflip(), g_random->coinflip());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("KeySel1", keysel1.toString()).detail("KeySel2", keysel2.toString());
|
|
std::stringstream ss;
|
|
ss << "(" << limits.rows << ", " << limits.minRows << ", " << limits.bytes << ")";
|
|
e.detail("Limits", ss.str());
|
|
}
|
|
};
|
|
|
|
struct TestGetRange2 : public BaseTest<TestGetRange2, Standalone<RangeResultRef>> {
|
|
typedef BaseTest<TestGetRange2, Standalone<RangeResultRef>> base_type;
|
|
Key key1, key2;
|
|
int limit;
|
|
|
|
TestGetRange2(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGetRange2") {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
limit = 0;
|
|
|
|
double lrv = g_random->random01();
|
|
if (lrv > 0.20) {
|
|
if (lrv < 0.4)
|
|
limit = g_random->randomInt(INT_MIN, 0);
|
|
else
|
|
limit = g_random->randomInt(0, INT_MAX)+1;
|
|
}
|
|
contract = {
|
|
std::make_pair( error_code_inverted_range, ExceptionContract::requiredIf(key1 > key2) ),
|
|
std::make_pair( error_code_range_limits_invalid, ExceptionContract::possibleButRequiredIf(limit < 0) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key1 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(key2 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->getRange(KeyRangeRef(key1, key2),
|
|
limit, g_random->coinflip(), g_random->coinflip());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key1", printable(key1)).detail("Key2", printable(key2)).detail("Limit", limit);
|
|
}
|
|
};
|
|
|
|
struct TestGetRange3 : public BaseTest<TestGetRange3, Standalone<RangeResultRef>> {
|
|
typedef BaseTest<TestGetRange3, Standalone<RangeResultRef>> base_type;
|
|
Key key1, key2;
|
|
GetRangeLimits limits;
|
|
|
|
TestGetRange3(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGetRange3") {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
limits = makeRangeLimits();
|
|
contract = {
|
|
std::make_pair( error_code_inverted_range, ExceptionContract::requiredIf(key1 > key2) ),
|
|
std::make_pair( error_code_range_limits_invalid, ExceptionContract::possibleButRequiredIf( !limits.isReached() && !limits.isValid()) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key1 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(key2 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->getRange(KeyRangeRef(key1, key2), limits, g_random->coinflip(), g_random->coinflip());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key1", printable(key1)).detail("Key2", printable(key2));
|
|
std::stringstream ss;
|
|
ss << "(" << limits.rows << ", " << limits.minRows << ", " << limits.bytes << ")";
|
|
e.detail("Limits", ss.str());
|
|
}
|
|
};
|
|
|
|
struct TestGetAddressesForKey : public BaseTest<TestGetAddressesForKey, Standalone<VectorRef<const char*>>> {
|
|
typedef BaseTest<TestGetAddressesForKey, Standalone<VectorRef<const char *>>> base_type;
|
|
Key key;
|
|
|
|
TestGetAddressesForKey(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestGetAddressesForKey") {
|
|
key = makeKey();
|
|
contract = {
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->getAddressesForKey(key);
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key", printable(key));
|
|
}
|
|
};
|
|
|
|
struct TestAddReadConflictRange : public BaseTestCallback<TestAddReadConflictRange> {
|
|
typedef BaseTest<TestAddReadConflictRange, Void> base_type;
|
|
Key key1, key2;
|
|
|
|
TestAddReadConflictRange(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestAddReadConflictRange") {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
contract = {
|
|
std::make_pair( error_code_inverted_range, ExceptionContract::requiredIf(key1 > key2) ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key1 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(key2 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->addReadConflictRange(KeyRangeRef(key1, key2));
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key1", printable(key1)).detail("Key2", printable(key2));
|
|
}
|
|
};
|
|
|
|
struct TestAtomicOp : public BaseTestCallback<TestAtomicOp> {
|
|
typedef BaseTest<TestAtomicOp, Void> base_type;
|
|
Key key;
|
|
Value value;
|
|
uint8_t op;
|
|
int32_t pos;
|
|
|
|
TestAtomicOp(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestAtomicOp") {
|
|
key = makeKey();
|
|
while (isProtectedKey(key)) {
|
|
key = makeKey();
|
|
}
|
|
value = makeValue();
|
|
double arv = g_random->random01();
|
|
if (arv < 0.25) {
|
|
int val = UINT8_MAX;
|
|
for (auto i = FDBMutationTypes::optionInfo.begin(); i != FDBMutationTypes::optionInfo.end(); ++i)
|
|
if (i->first < val)
|
|
val = i->first;
|
|
op = g_random->randomInt(0, val);
|
|
} else if (arv < 0.50) {
|
|
int val = 0;
|
|
for (auto i = FDBMutationTypes::optionInfo.begin(); i != FDBMutationTypes::optionInfo.end(); ++i)
|
|
if (i->first > val)
|
|
val = i->first;
|
|
op = g_random->randomInt(val+1, UINT8_MAX);
|
|
} else {
|
|
int minval = UINT8_MAX, maxval = 0;
|
|
for (auto i = FDBMutationTypes::optionInfo.begin(); i != FDBMutationTypes::optionInfo.end(); ++i) {
|
|
if (i->first < minval)
|
|
minval = i->first;
|
|
if (i->first > maxval)
|
|
maxval = i->first;
|
|
}
|
|
op = g_random->randomInt(minval, maxval+1);
|
|
}
|
|
|
|
pos = -1;
|
|
if(op == MutationRef::SetVersionstampedKey && key.size() >= 4) {
|
|
pos = littleEndian32(*(int32_t*)&key.end()[-4]);
|
|
}
|
|
if(op == MutationRef::SetVersionstampedValue && value.size() >= 4) {
|
|
pos = littleEndian32(*(int32_t*)&value.end()[-4]);
|
|
}
|
|
|
|
contract = {
|
|
std::make_pair( error_code_key_too_large, ExceptionContract::requiredIf(key.size() > (key.startsWith(systemKeys.begin) ? CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT : CLIENT_KNOBS->KEY_SIZE_LIMIT)) ),
|
|
std::make_pair( error_code_value_too_large, ExceptionContract::requiredIf(value.size() > CLIENT_KNOBS->VALUE_SIZE_LIMIT) ),
|
|
std::make_pair( error_code_invalid_mutation_type, ExceptionContract::requiredIf(
|
|
!isValidMutationType(op) || !isAtomicOp((MutationRef::Type) op)) ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key >= (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::requiredIf(
|
|
(op == MutationRef::SetVersionstampedKey && (pos < 0 || pos + 10 > key.size() - 4)) ||
|
|
(op == MutationRef::SetVersionstampedValue && (pos < 0 || pos + 10 > value.size() - 4))) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->atomicOp(key, value, (FDBMutationTypes::Option) op);
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key", printable(key)).detail("Value", printable(value)).detail("Op", op).detail("Pos", pos);
|
|
}
|
|
};
|
|
|
|
struct TestSet : public BaseTestCallback<TestSet> {
|
|
typedef BaseTest<TestSet, Void> base_type;
|
|
Key key;
|
|
Value value;
|
|
|
|
TestSet(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestSet") {
|
|
key = makeKey();
|
|
while (isProtectedKey(key)) {
|
|
key = makeKey();
|
|
}
|
|
value = makeValue();
|
|
contract = {
|
|
std::make_pair( error_code_key_too_large, ExceptionContract::requiredIf(key.size() > (key.startsWith(systemKeys.begin) ? CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT : CLIENT_KNOBS->KEY_SIZE_LIMIT)) ),
|
|
std::make_pair( error_code_value_too_large, ExceptionContract::requiredIf(value.size() > CLIENT_KNOBS->VALUE_SIZE_LIMIT) ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key >= (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->set(key, value);
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key", printable(key)).detail("Value", printable(value));
|
|
}
|
|
};
|
|
|
|
struct TestClear0 : public BaseTestCallback<TestClear0> {
|
|
typedef BaseTest<TestClear0, Void> base_type;
|
|
Key key1, key2;
|
|
|
|
TestClear0(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestClear0") {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
while (isProtectedKey(key1, key2)) {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
}
|
|
contract = {
|
|
std::make_pair( error_code_inverted_range, ExceptionContract::requiredIf(key1 > key2) ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key1 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(key2 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->clear(key1, key2);
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key1", printable(key1)).detail("Key2", printable(key2));
|
|
}
|
|
};
|
|
|
|
struct TestClear1 : public BaseTestCallback<TestClear1> {
|
|
typedef BaseTest<TestClear1, Void> base_type;
|
|
Key key1, key2;
|
|
|
|
TestClear1(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestClear1") {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
while (isProtectedKey(key1, key2)) {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
}
|
|
contract = {
|
|
std::make_pair( error_code_inverted_range, ExceptionContract::requiredIf(key1 > key2) ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key1 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(key2 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->clear(KeyRangeRef(key1, key2));
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key1", printable(key1)).detail("Key2", printable(key2));
|
|
}
|
|
};
|
|
|
|
struct TestClear2 : public BaseTestCallback<TestClear2> {
|
|
typedef BaseTest<TestClear2, Void> base_type;
|
|
Key key;
|
|
|
|
TestClear2(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestClear2") {
|
|
key = makeKey();
|
|
while (isProtectedKey(key)) {
|
|
key = makeKey();
|
|
}
|
|
contract = {
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
key >= (workload->useSystemKeys ? systemKeys.end : normalKeys.end) ) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->clear(key);
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key", printable(key));
|
|
}
|
|
};
|
|
|
|
struct TestWatch : public BaseTest<TestWatch, Void> {
|
|
typedef BaseTest<TestWatch, Void> base_type;
|
|
Key key;
|
|
|
|
TestWatch(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTest(id, workload, "TestWatch") {
|
|
key = makeKey();
|
|
contract = {
|
|
std::make_pair( error_code_key_too_large, ExceptionContract::requiredIf(key.size() > (key.startsWith(systemKeys.begin) ? CLIENT_KNOBS->SYSTEM_KEY_SIZE_LIMIT : CLIENT_KNOBS->KEY_SIZE_LIMIT)) ),
|
|
std::make_pair( error_code_watches_disabled, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf( (key >= (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_timed_out, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_accessed_unreadable, ExceptionContract::Possible )
|
|
};
|
|
}
|
|
|
|
ThreadFuture<value_type> createFuture(Reference<ITransaction> tr) {
|
|
return tr->watch(key);
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key", printable(key));
|
|
}
|
|
};
|
|
|
|
struct TestAddWriteConflictRange : public BaseTestCallback<TestAddWriteConflictRange> {
|
|
typedef BaseTest<TestAddWriteConflictRange, Void> base_type;
|
|
Key key1, key2;
|
|
|
|
TestAddWriteConflictRange(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestAddWriteConflictRange") {
|
|
key1 = makeKey();
|
|
key2 = makeKey();
|
|
contract = {
|
|
std::make_pair( error_code_inverted_range, ExceptionContract::requiredIf(key1 > key2) ),
|
|
std::make_pair( error_code_key_outside_legal_range, ExceptionContract::requiredIf(
|
|
(key1 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end)) ||
|
|
(key2 > (workload->useSystemKeys ? systemKeys.end : normalKeys.end))) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->addWriteConflictRange(KeyRangeRef(key1, key2));
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Key1", printable(key1)).detail("Key2", printable(key2));
|
|
}
|
|
};
|
|
|
|
struct TestSetOption : public BaseTestCallback<TestSetOption> {
|
|
typedef BaseTest<TestSetOption, Void> base_type;
|
|
int op;
|
|
Optional<Standalone<StringRef>> val;
|
|
|
|
TestSetOption(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestSetOption") {
|
|
double arv = g_random->random01();
|
|
if (arv < 0.25) {
|
|
int val = INT_MAX;
|
|
for (auto i = FDBTransactionOptions::optionInfo.begin(); i != FDBTransactionOptions::optionInfo.end(); ++i)
|
|
if (i->first < val)
|
|
val = i->first;
|
|
op = g_random->randomInt(INT_MIN, val);
|
|
} else if (arv < 0.50) {
|
|
int val = INT_MIN;
|
|
for (auto i = FDBTransactionOptions::optionInfo.begin(); i != FDBTransactionOptions::optionInfo.end(); ++i)
|
|
if (i->first > val)
|
|
val = i->first;
|
|
op = g_random->randomInt(val+1, INT_MAX);
|
|
} else {
|
|
int minval = INT_MAX, maxval = INT_MIN;
|
|
for (auto i = FDBTransactionOptions::optionInfo.begin(); i != FDBTransactionOptions::optionInfo.end(); ++i) {
|
|
if (i->first < minval)
|
|
minval = i->first;
|
|
if (i->first > maxval)
|
|
maxval = i->first;
|
|
}
|
|
op = g_random->randomInt(minval, maxval+1);
|
|
}
|
|
if(op == FDBTransactionOptions::ACCESS_SYSTEM_KEYS || op == FDBTransactionOptions::READ_SYSTEM_KEYS) //do not test access system keys since the option is actually used by the workload
|
|
op = -1;
|
|
|
|
double orv = g_random->random01();
|
|
if (orv >= 0.25) {
|
|
int64_t value_size = g_random->randomInt64(0, CLIENT_KNOBS->VALUE_SIZE_LIMIT + 1) + CLIENT_KNOBS->VALUE_SIZE_LIMIT;
|
|
|
|
std::string svalue;
|
|
svalue.reserve(value_size);
|
|
for (size_t j = 0; j < value_size; ++j)
|
|
svalue.append(1, (char) g_random->randomInt(0, 256));
|
|
|
|
val = Standalone<StringRef>(StringRef(svalue));
|
|
}
|
|
|
|
contract = {
|
|
std::make_pair( error_code_invalid_option_value, ExceptionContract::Possible ),
|
|
std::make_pair( error_code_client_invalid_operation, ExceptionContract::possibleIf((FDBTransactionOptions::Option)op == FDBTransactionOptions::READ_YOUR_WRITES_DISABLE) ),
|
|
std::make_pair( error_code_read_version_already_set, ExceptionContract::possibleIf((FDBTransactionOptions::Option)op == FDBTransactionOptions::INITIALIZE_NEW_DATABASE) )
|
|
};
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->setOption((FDBTransactionOptions::Option) op, val.cast_to<StringRef>());
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("Op", op).detail("Val", printable(val));
|
|
}
|
|
};
|
|
|
|
struct TestOnError : public BaseTestCallback<TestOnError> {
|
|
typedef BaseTest<TestOnError, Void> base_type;
|
|
int errorcode;
|
|
|
|
TestOnError(unsigned int id, FuzzApiCorrectnessWorkload *workload) : BaseTestCallback(id, workload, "TestOnError") {
|
|
errorcode = 0;
|
|
double erv = g_random->random01();
|
|
if (erv >= 0.2) {
|
|
if (erv < 0.6)
|
|
errorcode = g_random->randomInt(INT_MIN, 0);
|
|
else
|
|
errorcode = g_random->randomInt(0, INT_MAX)+1;
|
|
}
|
|
}
|
|
|
|
void callback(Reference<ITransaction> tr) {
|
|
tr->onError(Error::fromUnvalidatedCode(errorcode));
|
|
// This is necessary here, as onError will have reset this
|
|
// value, we will be looking at the wrong thing.
|
|
if( workload->useSystemKeys )
|
|
tr->setOption( FDBTransactionOptions::ACCESS_SYSTEM_KEYS );
|
|
}
|
|
|
|
void augmentTrace(TraceEvent &e) const {
|
|
base_type::augmentTrace(e);
|
|
e.detail("ErrorCode", errorcode);
|
|
}
|
|
};
|
|
|
|
static void addTestCases() {
|
|
testCases.push_back(std::bind(&TestSetVersion::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGet::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGetKey::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGetRange0::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGetRange1::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGetRange2::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGetRange3::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestGetAddressesForKey::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestAddReadConflictRange::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestAtomicOp::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestSet::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestClear0::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestClear1::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestClear2::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestWatch::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestAddWriteConflictRange::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestSetOption::runTest, ph::_1, ph::_2, ph::_3));
|
|
testCases.push_back(std::bind(&TestOnError::runTest, ph::_1, ph::_2, ph::_3));
|
|
}
|
|
};
|
|
|
|
std::once_flag FuzzApiCorrectnessWorkload::onceFlag;
|
|
std::vector<std::function<Future<Void> (unsigned int const &, FuzzApiCorrectnessWorkload * const &, Reference<ITransaction> const &)>> FuzzApiCorrectnessWorkload::testCases;
|
|
WorkloadFactory<FuzzApiCorrectnessWorkload> FuzzApiCorrectnessWorkloadFactory("FuzzApiCorrectness");
|