blob: 04f2cb1bda91cec94c22f6a4986cd778729a584c [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.
*/
// Author: jmarantz@google.com (Joshua Marantz)
// Shared infrastructure for testing cache implementations
#ifndef PAGESPEED_KERNEL_CACHE_CACHE_TEST_BASE_H_
#define PAGESPEED_KERNEL_CACHE_CACHE_TEST_BASE_H_
#include <vector>
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/cache_interface.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/null_mutex.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/shared_string.h"
#include "pagespeed/kernel/base/stl_util.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
namespace net_instaweb {
class CacheTestBase : public testing::Test {
public:
// Helper class for calling Get on cache implementations that are blocking
// in nature (e.g. in-memory LRU or blocking file-system). Also tests the
// CacheInterface::SynchronousCallback class in the process.
class Callback : public CacheInterface::SynchronousCallback {
public:
explicit Callback(CacheTestBase* test) : test_(test) { Reset(); }
Callback() : test_(NULL) { Reset(); }
virtual ~Callback() {}
Callback* Reset() {
SynchronousCallback::Reset();
validate_called_ = false;
noop_wait_called_ = false;
value_of_called_when_wait_was_invoked_ = false;
invalid_value_ = NULL;
return this;
}
virtual bool ValidateCandidate(const GoogleString& key,
CacheInterface::KeyState state) {
validate_called_ = true;
if ((invalid_value_ != NULL) && (value_str() == invalid_value_)) {
return false;
}
return true;
}
virtual void Done(CacheInterface::KeyState state) {
SynchronousCallback::Done(state);
EXPECT_TRUE(validate_called_);
if (test_ != NULL) {
test_->GetDone();
}
}
// The default implementation has an empty Wait implementation.
// If you override this, be sure also to call set_mutex() from the
// test subclass constructor or SetUp to protect outstanding_fetches_.
virtual void Wait() {
noop_wait_called_ = true;
value_of_called_when_wait_was_invoked_ = called();
}
void set_invalid_value(const char* v) { invalid_value_ = v; }
StringPiece value_str() { return value()->Value(); }
bool validate_called_;
bool noop_wait_called_;
bool value_of_called_when_wait_was_invoked_;
private:
CacheTestBase* test_;
const char* invalid_value_;
DISALLOW_COPY_AND_ASSIGN(Callback);
};
protected:
CacheTestBase()
: invalid_value_(NULL),
mutex_(new NullMutex),
outstanding_fetches_(0) {
}
~CacheTestBase() {
STLDeleteElements(&callbacks_);
}
// Allocates a callback structure. The default Callback structure
// has an empty implementation of Wait().
virtual Callback* NewCallback() { return new Callback(this); }
virtual CacheInterface* Cache() = 0;
// Optional method that can be specified by a test subclass to specify
// an operation that should be performed after Get or Put.
virtual void PostOpCleanup() {}
// Performs a cache Get, waits for callback completion, and checks the
// result is as expected.
void CheckGet(const GoogleString& key, const GoogleString& expected_value) {
CheckGet(Cache(), key, expected_value);
}
// As above, but specifies which cache to use.
void CheckGet(CacheInterface* cache, const GoogleString& key,
const GoogleString& expected_value) {
Callback* callback = InitiateGet(cache, key);
WaitAndCheck(callback, expected_value);
}
// Writes a value into the cache.
void CheckPut(const GoogleString& key, const GoogleString& value) {
CheckPut(Cache(), key, value);
}
void CheckPut(CacheInterface* cache, const GoogleString& key,
const GoogleString& value) {
SharedString put_buffer(value);
cache->Put(key, &put_buffer);
PostOpCleanup();
}
void CheckDelete(const GoogleString& key) {
Cache()->Delete(key);
PostOpCleanup();
}
// Performs a Get and verifies that the key is not found.
void CheckNotFound(const char* key) {
CheckNotFound(Cache(), key);
}
void CheckNotFound(CacheInterface* cache, const char* key) {
Callback* callback = InitiateGet(cache, key);
WaitAndCheckNotFound(callback);
}
// Adds a new callback to the callback-array, returning a Callback*
// that can then be passed to WaitAndCheck and WaitAndCheckNotFound.
Callback* AddCallback() {
Callback* callback = NewCallback();
callback->set_invalid_value(invalid_value_);
callbacks_.push_back(callback);
return callback;
}
void WaitAndCheck(Callback* callback, const GoogleString& expected_value) {
// noop_wait_called_ means the callback hasn't overridden Wait() so
// Callback's do-nothing method was called; if that's the case, then
// we expect the cache to be blocking, and we expect the Get() call
// to have invoked Done() before the do-nothing Wait(). Since we can't
// know which version of Wait() was called before calling it, we use the
// value of called() as noted by the do-nothing Wait().
callback->Wait();
if (callback->noop_wait_called_) {
EXPECT_TRUE(callback->value_of_called_when_wait_was_invoked_);
// We'd like to test this but it's valid for a CacheInterface to report
// that it is not blocking even though it actually is (or can be).
// DelayCache is an example.
// EXPECT_TRUE(Cache()->IsBlocking());
}
ASSERT_TRUE(callback->called());
EXPECT_STREQ(expected_value, callback->value_str());
EXPECT_EQ(CacheInterface::kAvailable, callback->state());
PostOpCleanup();
}
void WaitAndCheckNotFound(Callback* callback) {
callback->Wait();
ASSERT_TRUE(callback->called());
EXPECT_EQ(CacheInterface::kNotFound, callback->state());
PostOpCleanup();
}
void IssueMultiGet(Callback* c0, const GoogleString& key0,
Callback* c1, const GoogleString& key1,
Callback* c2, const GoogleString& key2) {
CacheInterface::MultiGetRequest* request =
new CacheInterface::MultiGetRequest;
request->push_back(CacheInterface::KeyCallback(key0, c0));
request->push_back(CacheInterface::KeyCallback(key1, c1));
request->push_back(CacheInterface::KeyCallback(key2, c2));
Cache()->MultiGet(request);
}
void TestMultiGet() {
PopulateCache(2);
Callback* n0 = AddCallback();
Callback* not_found = AddCallback();
Callback* n1 = AddCallback();
IssueMultiGet(n0, "n0", not_found, "not_found", n1, "n1");
WaitAndCheck(n0, "v0");
WaitAndCheckNotFound(not_found);
WaitAndCheck(n1, "v1");
}
// Populates the cache with keys in pattern n0 n1 n2 n3...
// and values in pattern v0 v1 v2 v3...
void PopulateCache(int num) {
for (int i = 0; i < num; ++i) {
CheckPut(StringPrintf("n%d", i), StringPrintf("v%d", i));
}
}
void set_invalid_value(const char* v) { invalid_value_ = v; }
// Initiate a cache Get, and return the Callback* which can be
// passed to WaitAndCheck or WaitAndCheckNotFound.
Callback* InitiateGet(const GoogleString& key) {
return InitiateGet(Cache(), key);
}
Callback* InitiateGet(CacheInterface* cache, const GoogleString& key) {
{
ScopedMutex lock(mutex_.get());
++outstanding_fetches_;
}
Callback* callback = AddCallback();
cache->Get(key, callback);
return callback;
}
// Sets the mutex used to protect outstanding_fetches_.
void set_mutex(AbstractMutex* mutex) { mutex_.reset(mutex); }
// Returns the number of outstanding Get requests. The return value
// makes sense only if the cache system is quiescent.
int outstanding_fetches() {
ScopedMutex lock(mutex_.get());
return outstanding_fetches_;
}
private:
// Called from Callback::Done to track outstanding fetches -- for
// callbacks created with NewCallback().
//
// TODO(jmarantz): eliminate the default ctor for Callback and change
// all cache tests to use NewCallback() to remove that special case.
void GetDone() {
ScopedMutex lock(mutex_.get());
--outstanding_fetches_;
}
const char* invalid_value_; // may be NULL.
std::vector<Callback*> callbacks_;
scoped_ptr<AbstractMutex> mutex_;
int outstanding_fetches_; // protected by mutex_
};
} // namespace net_instaweb
#endif // PAGESPEED_KERNEL_CACHE_CACHE_TEST_BASE_H_