| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/basictypes.h" |
| #include "base/mac/scoped_nsautorelease_pool.h" |
| #include "base/platform_thread.h" |
| #include "base/shared_memory.h" |
| #include "base/scoped_ptr.h" |
| #include "base/test/multiprocess_test.h" |
| #include "base/time.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/multiprocess_func_list.h" |
| |
| static const int kNumThreads = 5; |
| static const int kNumTasks = 5; |
| |
| namespace base { |
| |
| namespace { |
| |
| // Each thread will open the shared memory. Each thread will take a different 4 |
| // byte int pointer, and keep changing it, with some small pauses in between. |
| // Verify that each thread's value in the shared memory is always correct. |
| class MultipleThreadMain : public PlatformThread::Delegate { |
| public: |
| explicit MultipleThreadMain(int16 id) : id_(id) {} |
| ~MultipleThreadMain() {} |
| |
| static void CleanUp() { |
| SharedMemory memory; |
| memory.Delete(s_test_name_); |
| } |
| |
| // PlatformThread::Delegate interface. |
| void ThreadMain() { |
| mac::ScopedNSAutoreleasePool pool; // noop if not OSX |
| const uint32 kDataSize = 1024; |
| SharedMemory memory; |
| bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); |
| EXPECT_TRUE(rv); |
| rv = memory.Map(kDataSize); |
| EXPECT_TRUE(rv); |
| int *ptr = static_cast<int*>(memory.memory()) + id_; |
| EXPECT_EQ(*ptr, 0); |
| |
| for (int idx = 0; idx < 100; idx++) { |
| *ptr = idx; |
| PlatformThread::Sleep(1); // Short wait. |
| EXPECT_EQ(*ptr, idx); |
| } |
| |
| memory.Close(); |
| } |
| |
| private: |
| int16 id_; |
| |
| static const char* const s_test_name_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MultipleThreadMain); |
| }; |
| |
| const char* const MultipleThreadMain::s_test_name_ = |
| "SharedMemoryOpenThreadTest"; |
| |
| // TODO(port): |
| // This test requires the ability to pass file descriptors between processes. |
| // We haven't done that yet in Chrome for POSIX. |
| #if defined(OS_WIN) |
| // Each thread will open the shared memory. Each thread will take the memory, |
| // and keep changing it while trying to lock it, with some small pauses in |
| // between. Verify that each thread's value in the shared memory is always |
| // correct. |
| class MultipleLockThread : public PlatformThread::Delegate { |
| public: |
| explicit MultipleLockThread(int id) : id_(id) {} |
| ~MultipleLockThread() {} |
| |
| // PlatformThread::Delegate interface. |
| void ThreadMain() { |
| const uint32 kDataSize = sizeof(int); |
| SharedMemoryHandle handle = NULL; |
| { |
| SharedMemory memory1; |
| EXPECT_TRUE(memory1.CreateNamed("SharedMemoryMultipleLockThreadTest", |
| true, kDataSize)); |
| EXPECT_TRUE(memory1.ShareToProcess(GetCurrentProcess(), &handle)); |
| // TODO(paulg): Implement this once we have a posix version of |
| // SharedMemory::ShareToProcess. |
| EXPECT_TRUE(true); |
| } |
| |
| SharedMemory memory2(handle, false); |
| EXPECT_TRUE(memory2.Map(kDataSize)); |
| volatile int* const ptr = static_cast<int*>(memory2.memory()); |
| |
| for (int idx = 0; idx < 20; idx++) { |
| memory2.Lock(); |
| int i = (id_ << 16) + idx; |
| *ptr = i; |
| PlatformThread::Sleep(1); // Short wait. |
| EXPECT_EQ(*ptr, i); |
| memory2.Unlock(); |
| } |
| |
| memory2.Close(); |
| } |
| |
| private: |
| int id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MultipleLockThread); |
| }; |
| #endif |
| |
| } // namespace |
| |
| TEST(SharedMemoryTest, OpenClose) { |
| const uint32 kDataSize = 1024; |
| std::string test_name = "SharedMemoryOpenCloseTest"; |
| |
| // Open two handles to a memory segment, confirm that they are mapped |
| // separately yet point to the same space. |
| SharedMemory memory1; |
| bool rv = memory1.Delete(test_name); |
| EXPECT_TRUE(rv); |
| rv = memory1.Delete(test_name); |
| EXPECT_TRUE(rv); |
| rv = memory1.Open(test_name, false); |
| EXPECT_FALSE(rv); |
| rv = memory1.CreateNamed(test_name, false, kDataSize); |
| EXPECT_TRUE(rv); |
| rv = memory1.Map(kDataSize); |
| EXPECT_TRUE(rv); |
| SharedMemory memory2; |
| rv = memory2.Open(test_name, false); |
| EXPECT_TRUE(rv); |
| rv = memory2.Map(kDataSize); |
| EXPECT_TRUE(rv); |
| EXPECT_NE(memory1.memory(), memory2.memory()); // Compare the pointers. |
| |
| // Make sure we don't segfault. (it actually happened!) |
| ASSERT_NE(memory1.memory(), static_cast<void*>(NULL)); |
| ASSERT_NE(memory2.memory(), static_cast<void*>(NULL)); |
| |
| // Write data to the first memory segment, verify contents of second. |
| memset(memory1.memory(), '1', kDataSize); |
| EXPECT_EQ(memcmp(memory1.memory(), memory2.memory(), kDataSize), 0); |
| |
| // Close the first memory segment, and verify the second has the right data. |
| memory1.Close(); |
| char *start_ptr = static_cast<char *>(memory2.memory()); |
| char *end_ptr = start_ptr + kDataSize; |
| for (char* ptr = start_ptr; ptr < end_ptr; ptr++) |
| EXPECT_EQ(*ptr, '1'); |
| |
| // Close the second memory segment. |
| memory2.Close(); |
| |
| rv = memory1.Delete(test_name); |
| EXPECT_TRUE(rv); |
| rv = memory2.Delete(test_name); |
| EXPECT_TRUE(rv); |
| } |
| |
| TEST(SharedMemoryTest, OpenExclusive) { |
| const uint32 kDataSize = 1024; |
| const uint32 kDataSize2 = 2048; |
| std::ostringstream test_name_stream; |
| test_name_stream << "SharedMemoryOpenExclusiveTest." |
| << Time::Now().ToDoubleT(); |
| std::string test_name = test_name_stream.str(); |
| |
| // Open two handles to a memory segment and check that open_existing works |
| // as expected. |
| SharedMemory memory1; |
| bool rv = memory1.CreateNamed(test_name, false, kDataSize); |
| EXPECT_TRUE(rv); |
| |
| // Memory1 knows it's size because it created it. |
| EXPECT_EQ(memory1.created_size(), kDataSize); |
| |
| rv = memory1.Map(kDataSize); |
| EXPECT_TRUE(rv); |
| |
| memset(memory1.memory(), 'G', kDataSize); |
| |
| SharedMemory memory2; |
| // Should not be able to create if openExisting is false. |
| rv = memory2.CreateNamed(test_name, false, kDataSize2); |
| EXPECT_FALSE(rv); |
| |
| // Should be able to create with openExisting true. |
| rv = memory2.CreateNamed(test_name, true, kDataSize2); |
| EXPECT_TRUE(rv); |
| |
| // Memory2 shouldn't know the size because we didn't create it. |
| EXPECT_EQ(memory2.created_size(), 0U); |
| |
| // We should be able to map the original size. |
| rv = memory2.Map(kDataSize); |
| EXPECT_TRUE(rv); |
| |
| // Verify that opening memory2 didn't truncate or delete memory 1. |
| char *start_ptr = static_cast<char *>(memory2.memory()); |
| char *end_ptr = start_ptr + kDataSize; |
| for (char* ptr = start_ptr; ptr < end_ptr; ptr++) { |
| EXPECT_EQ(*ptr, 'G'); |
| } |
| |
| memory1.Close(); |
| memory2.Close(); |
| |
| rv = memory1.Delete(test_name); |
| EXPECT_TRUE(rv); |
| } |
| |
| // Create a set of N threads to each open a shared memory segment and write to |
| // it. Verify that they are always reading/writing consistent data. |
| TEST(SharedMemoryTest, MultipleThreads) { |
| MultipleThreadMain::CleanUp(); |
| // On POSIX we have a problem when 2 threads try to create the shmem |
| // (a file) at exactly the same time, since create both creates the |
| // file and zerofills it. We solve the problem for this unit test |
| // (make it not flaky) by starting with 1 thread, then |
| // intentionally don't clean up its shmem before running with |
| // kNumThreads. |
| |
| int threadcounts[] = { 1, kNumThreads }; |
| for (size_t i = 0; i < sizeof(threadcounts) / sizeof(threadcounts); i++) { |
| int numthreads = threadcounts[i]; |
| scoped_array<PlatformThreadHandle> thread_handles; |
| scoped_array<MultipleThreadMain*> thread_delegates; |
| |
| thread_handles.reset(new PlatformThreadHandle[numthreads]); |
| thread_delegates.reset(new MultipleThreadMain*[numthreads]); |
| |
| // Spawn the threads. |
| for (int16 index = 0; index < numthreads; index++) { |
| PlatformThreadHandle pth; |
| thread_delegates[index] = new MultipleThreadMain(index); |
| EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); |
| thread_handles[index] = pth; |
| } |
| |
| // Wait for the threads to finish. |
| for (int index = 0; index < numthreads; index++) { |
| PlatformThread::Join(thread_handles[index]); |
| delete thread_delegates[index]; |
| } |
| } |
| MultipleThreadMain::CleanUp(); |
| } |
| |
| // TODO(port): this test requires the MultipleLockThread class |
| // (defined above), which requires the ability to pass file |
| // descriptors between processes. We haven't done that yet in Chrome |
| // for POSIX. |
| #if defined(OS_WIN) |
| // Create a set of threads to each open a shared memory segment and write to it |
| // with the lock held. Verify that they are always reading/writing consistent |
| // data. |
| TEST(SharedMemoryTest, Lock) { |
| PlatformThreadHandle thread_handles[kNumThreads]; |
| MultipleLockThread* thread_delegates[kNumThreads]; |
| |
| // Spawn the threads. |
| for (int index = 0; index < kNumThreads; ++index) { |
| PlatformThreadHandle pth; |
| thread_delegates[index] = new MultipleLockThread(index); |
| EXPECT_TRUE(PlatformThread::Create(0, thread_delegates[index], &pth)); |
| thread_handles[index] = pth; |
| } |
| |
| // Wait for the threads to finish. |
| for (int index = 0; index < kNumThreads; ++index) { |
| PlatformThread::Join(thread_handles[index]); |
| delete thread_delegates[index]; |
| } |
| } |
| #endif |
| |
| // Allocate private (unique) shared memory with an empty string for a |
| // name. Make sure several of them don't point to the same thing as |
| // we might expect if the names are equal. |
| TEST(SharedMemoryTest, AnonymousPrivate) { |
| int i, j; |
| int count = 4; |
| bool rv; |
| const uint32 kDataSize = 8192; |
| |
| scoped_array<SharedMemory> memories(new SharedMemory[count]); |
| scoped_array<int*> pointers(new int*[count]); |
| ASSERT_TRUE(memories.get()); |
| ASSERT_TRUE(pointers.get()); |
| |
| for (i = 0; i < count; i++) { |
| rv = memories[i].CreateAndMapAnonymous(kDataSize); |
| EXPECT_TRUE(rv); |
| int *ptr = static_cast<int*>(memories[i].memory()); |
| EXPECT_TRUE(ptr); |
| pointers[i] = ptr; |
| } |
| |
| for (i = 0; i < count; i++) { |
| // zero out the first int in each except for i; for that one, make it 100. |
| for (j = 0; j < count; j++) { |
| if (i == j) |
| pointers[j][0] = 100; |
| else |
| pointers[j][0] = 0; |
| } |
| // make sure there is no bleeding of the 100 into the other pointers |
| for (j = 0; j < count; j++) { |
| if (i == j) |
| EXPECT_EQ(100, pointers[j][0]); |
| else |
| EXPECT_EQ(0, pointers[j][0]); |
| } |
| } |
| |
| for (int i = 0; i < count; i++) { |
| memories[i].Close(); |
| } |
| } |
| |
| // On POSIX it is especially important we test shmem across processes, |
| // not just across threads. But the test is enabled on all platforms. |
| class SharedMemoryProcessTest : public base::MultiProcessTest { |
| public: |
| |
| static void CleanUp() { |
| SharedMemory memory; |
| memory.Delete(s_test_name_); |
| } |
| |
| static int TaskTestMain() { |
| int errors = 0; |
| mac::ScopedNSAutoreleasePool pool; // noop if not OSX |
| const uint32 kDataSize = 1024; |
| SharedMemory memory; |
| bool rv = memory.CreateNamed(s_test_name_, true, kDataSize); |
| EXPECT_TRUE(rv); |
| if (rv != true) |
| errors++; |
| rv = memory.Map(kDataSize); |
| EXPECT_TRUE(rv); |
| if (rv != true) |
| errors++; |
| int *ptr = static_cast<int*>(memory.memory()); |
| |
| for (int idx = 0; idx < 20; idx++) { |
| memory.Lock(); |
| int i = (1 << 16) + idx; |
| *ptr = i; |
| PlatformThread::Sleep(10); // Short wait. |
| if (*ptr != i) |
| errors++; |
| memory.Unlock(); |
| } |
| |
| memory.Close(); |
| return errors; |
| } |
| |
| private: |
| static const char* const s_test_name_; |
| }; |
| |
| const char* const SharedMemoryProcessTest::s_test_name_ = "MPMem"; |
| |
| |
| #if defined(OS_MACOSX) |
| #define MAYBE_Tasks FLAKY_Tasks |
| #else |
| #define MAYBE_Tasks Tasks |
| #endif |
| |
| TEST_F(SharedMemoryProcessTest, MAYBE_Tasks) { |
| SharedMemoryProcessTest::CleanUp(); |
| |
| base::ProcessHandle handles[kNumTasks]; |
| for (int index = 0; index < kNumTasks; ++index) { |
| handles[index] = SpawnChild("SharedMemoryTestMain", false); |
| } |
| |
| int exit_code = 0; |
| for (int index = 0; index < kNumTasks; ++index) { |
| EXPECT_TRUE(base::WaitForExitCode(handles[index], &exit_code)); |
| EXPECT_TRUE(exit_code == 0); |
| } |
| |
| SharedMemoryProcessTest::CleanUp(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(SharedMemoryTestMain) { |
| return SharedMemoryProcessTest::TaskTestMain(); |
| } |
| |
| } // namespace base |