blob: 2ce2c483b9afd1c7742e3fd61e7ae78a3bcab7e5 [file] [log] [blame]
// Copyright 2011 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: morlovich@google.com (Maksim Orlovich)
#include "pagespeed/kernel/sharedmem/shared_mem_test_base.h"
#include <cstddef>
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/abstract_shared_mem.h"
#include "pagespeed/kernel/base/function.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/thread_annotations.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/util/platform.h"
namespace net_instaweb {
namespace {
const char kTestSegment[] = "segment1";
const char kOtherSegment[] = "segment2";
} // namespace
SharedMemTestEnv::~SharedMemTestEnv() {
}
SharedMemTestBase::SharedMemTestBase(SharedMemTestEnv* test_env)
: test_env_(test_env),
shmem_runtime_(test_env->CreateSharedMemRuntime()),
thread_system_(Platform::CreateThreadSystem()),
handler_(thread_system_->NewMutex()) {
}
bool SharedMemTestBase::CreateChild(TestMethod method) {
Function* callback = new MemberFunction0<SharedMemTestBase>(method, this);
return test_env_->CreateChild(callback);
}
void SharedMemTestBase::TestReadWrite(bool reattach) {
scoped_ptr<AbstractSharedMemSegment> seg(CreateDefault());
ASSERT_TRUE(seg.get() != NULL);
ASSERT_TRUE(CreateChild(&SharedMemTestBase::TestReadWriteChild));
if (reattach) {
seg.reset(AttachDefault());
}
// Wait for kid to write out stuff
while (*seg->Base() != '1') {
test_env_->ShortSleep();
}
// Write out stuff.
*seg->Base() = '2';
// Wait for termination.
test_env_->WaitForChildren();
seg.reset(NULL);
DestroyDefault();
EXPECT_EQ(0, handler_.SeriousMessages());
}
void SharedMemTestBase::TestReadWriteChild() {
scoped_ptr<AbstractSharedMemSegment> seg(AttachDefault());
// Write out '1', which the parent will wait for.
*seg->Base() = '1';
// Wait for '2' from parent
while (*seg->Base() != '2') {
test_env_->ShortSleep();
}
}
void SharedMemTestBase::TestLarge() {
scoped_ptr<AbstractSharedMemSegment> seg(
shmem_runtime_->CreateSegment(kTestSegment, kLarge, &handler_));
ASSERT_TRUE(seg.get() != NULL);
// Make sure everything is zeroed
for (int c = 0; c < kLarge; ++c) {
EXPECT_EQ(0, seg->Base()[c]);
}
seg.reset(NULL);
ASSERT_TRUE(CreateChild(&SharedMemTestBase::TestLargeChild));
test_env_->WaitForChildren();
seg.reset(shmem_runtime_->AttachToSegment(kTestSegment, kLarge, &handler_));
for (int i = 0; i < kLarge; i+=4) {
EXPECT_EQ(i, *IntPtr(seg.get(), i));
}
DestroyDefault();
}
void SharedMemTestBase::TestLargeChild() {
scoped_ptr<AbstractSharedMemSegment> seg(
shmem_runtime_->AttachToSegment(kTestSegment, kLarge, &handler_));
for (int i = 0; i < kLarge; i+=4) {
*IntPtr(seg.get(), i) = i;
}
}
// Make sure that 2 segments don't interfere.
void SharedMemTestBase::TestDistinct() {
scoped_ptr<AbstractSharedMemSegment> seg(CreateDefault());
ASSERT_TRUE(seg.get() != NULL);
scoped_ptr<AbstractSharedMemSegment> seg2(
shmem_runtime_->CreateSegment(kOtherSegment, 4, &handler_));
ASSERT_TRUE(seg2.get() != NULL);
ASSERT_TRUE(CreateChild(&SharedMemTestBase::WriteSeg1Child));
ASSERT_TRUE(CreateChild(&SharedMemTestBase::WriteSeg2Child));
test_env_->WaitForChildren();
EXPECT_EQ('1', *seg->Base());
EXPECT_EQ('2', *seg2->Base());
seg.reset(NULL);
seg2.reset(NULL);
DestroyDefault();
shmem_runtime_->DestroySegment(kOtherSegment, &handler_);
EXPECT_EQ(0, handler_.SeriousMessages());
}
// Make sure destruction destroys things properly...
void SharedMemTestBase::TestDestroy() {
scoped_ptr<AbstractSharedMemSegment> seg(CreateDefault());
ASSERT_TRUE(seg.get() != NULL);
ASSERT_TRUE(CreateChild(&SharedMemTestBase::WriteSeg1Child));
test_env_->WaitForChildren();
EXPECT_EQ('1', *seg->Base());
seg.reset(NULL);
DestroyDefault();
// Attach should fail now
seg.reset(AttachDefault());
EXPECT_EQ(NULL, seg.get());
// Newly created one should have zeroed memory
seg.reset(CreateDefault());
EXPECT_EQ('\0', *seg->Base());
DestroyDefault();
}
// Make sure that re-creating a segment without a Destroy is safe and
// produces a distinct segment
void SharedMemTestBase::TestCreateTwice() {
scoped_ptr<AbstractSharedMemSegment> seg(CreateDefault());
ASSERT_TRUE(seg.get() != NULL);
ASSERT_TRUE(CreateChild(&SharedMemTestBase::WriteSeg1Child));
test_env_->WaitForChildren();
EXPECT_EQ('1', *seg->Base());
seg.reset(CreateDefault());
EXPECT_EQ('\0', *seg->Base());
DestroyDefault();
}
// Make sure between two kids see the SHM as well.
void SharedMemTestBase::TestTwoKids() {
scoped_ptr<AbstractSharedMemSegment> seg(CreateDefault());
ASSERT_TRUE(seg.get() != NULL);
seg.reset(NULL);
ASSERT_TRUE(CreateChild(&SharedMemTestBase::TwoKidsChild1));
ASSERT_TRUE(CreateChild(&SharedMemTestBase::TwoKidsChild2));
test_env_->WaitForChildren();
seg.reset(AttachDefault());
EXPECT_EQ('2', *seg->Base());
DestroyDefault();
EXPECT_EQ(0, handler_.SeriousMessages());
}
void SharedMemTestBase::TwoKidsChild1() {
scoped_ptr<AbstractSharedMemSegment> seg(AttachDefault());
ASSERT_TRUE(seg.get() != NULL);
// Write out '1', which the other kid will wait for.
*seg->Base() = '1';
}
void SharedMemTestBase::TwoKidsChild2() {
scoped_ptr<AbstractSharedMemSegment> seg(AttachDefault());
ASSERT_TRUE(seg.get() != NULL);
// Wait for '1'
while (*seg->Base() != '1') {
test_env_->ShortSleep();
}
*seg->Base() = '2';
}
// Test for mutex operation. This attempts to detect lack of mutual exclusion
// by hammering on a shared location (protected by a lock) with non-atomic
// increments. This test does not guarantee that it will detect a failure
// (the schedule might just end up such that things work out), but it's
// been found to be effective in practice.
void SharedMemTestBase::TestMutex() NO_THREAD_SAFETY_ANALYSIS {
size_t mutex_size = shmem_runtime_->SharedMutexSize();
scoped_ptr<AbstractSharedMemSegment> seg(
shmem_runtime_->CreateSegment(kTestSegment, mutex_size + 4, &handler_));
ASSERT_TRUE(seg.get() != NULL);
ASSERT_EQ(mutex_size, seg->SharedMutexSize());
ASSERT_TRUE(seg->InitializeSharedMutex(0, &handler_));
seg.reset(
shmem_runtime_->AttachToSegment(kTestSegment, mutex_size + 4, &handler_));
scoped_ptr<AbstractMutex> mutex(seg->AttachToSharedMutex(0));
mutex->Lock();
ASSERT_TRUE(CreateChild(&SharedMemTestBase::MutexChild));
// Unblock the kid. Before that, it shouldn't have written
EXPECT_EQ(0, *IntPtr(seg.get(), mutex_size));
mutex->Unlock();
mutex->Lock();
EXPECT_TRUE(IncrementStorm(seg.get(), mutex_size));
mutex->Unlock();
test_env_->WaitForChildren();
DestroyDefault();
}
void SharedMemTestBase::MutexChild() {
size_t mutex_size = shmem_runtime_->SharedMutexSize();
scoped_ptr<AbstractSharedMemSegment> seg(
shmem_runtime_->AttachToSegment(kTestSegment, mutex_size + 4, &handler_));
ASSERT_TRUE(seg.get() != NULL);
scoped_ptr<AbstractMutex> mutex(seg->AttachToSharedMutex(0));
mutex->Lock();
if (!IncrementStorm(seg.get(), mutex_size)) {
mutex->Unlock();
test_env_->ChildFailed();
return;
}
mutex->Unlock();
}
// Returns if successful
bool SharedMemTestBase::IncrementStorm(AbstractSharedMemSegment* seg,
size_t mutex_size) {
// We are either the first or second to do the increments.
int init = *IntPtr(seg, mutex_size);
if ((init != 0) && (init != kNumIncrements)) {
return false;
}
for (int i = 0; i < kNumIncrements; ++i) {
++*IntPtr(seg, mutex_size);
if (*IntPtr(seg, mutex_size) != (i + init + 1)) {
return false;
}
++*IntPtr(seg, mutex_size);
if (*IntPtr(seg, mutex_size) != (i + init + 2)) {
return false;
}
--*IntPtr(seg, mutex_size);
if (*IntPtr(seg, mutex_size) != (i + init + 1)) {
return false;
}
}
return true;
}
void SharedMemTestBase::WriteSeg1Child() {
scoped_ptr<AbstractSharedMemSegment> seg(AttachDefault());
ASSERT_TRUE(seg.get() != NULL);
*seg->Base() = '1';
}
void SharedMemTestBase::WriteSeg2Child() {
scoped_ptr<AbstractSharedMemSegment> seg(
shmem_runtime_->AttachToSegment(kOtherSegment, 4, &handler_));
ASSERT_TRUE(seg.get() != NULL);
*seg->Base() = '2';
}
AbstractSharedMemSegment* SharedMemTestBase::CreateDefault() {
return shmem_runtime_->CreateSegment(kTestSegment, 4, &handler_);
}
AbstractSharedMemSegment* SharedMemTestBase::AttachDefault() {
return shmem_runtime_->AttachToSegment(kTestSegment, 4, &handler_);
}
void SharedMemTestBase::DestroyDefault() {
shmem_runtime_->DestroySegment(kTestSegment, &handler_);
}
} // namespace net_instaweb