// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. #include #include #include #include #include #include #include using namespace isc::util; using boost::scoped_ptr; namespace { const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped"; const size_t DEFAULT_INITIAL_SIZE = 32 * 1024; // intentionally hardcoded class MemorySegmentMappedTest : public ::testing::Test { protected: MemorySegmentMappedTest() { resetSegment(); } ~MemorySegmentMappedTest() { segment_.reset(); boost::interprocess::file_mapping::remove(mapped_file); } // For initialization and for tests after the segment possibly becomes // broken. void resetSegment() { segment_.reset(); boost::interprocess::file_mapping::remove(mapped_file); segment_.reset(new MemorySegmentMapped(mapped_file, true)); } scoped_ptr segment_; }; TEST_F(MemorySegmentMappedTest, createAndModify) { // We are going to do the same set of basic tests twice; one after creating // the mapped file, the other by re-opening the existing file in the // read-write mode. for (int i = 0; i < 2; ++i) { // It should have the default size (intentionally hardcoded) EXPECT_EQ(DEFAULT_INITIAL_SIZE, segment_->getSize()); // By default, nothing is allocated. EXPECT_TRUE(segment_->allMemoryDeallocated()); void* ptr = segment_->allocate(1024); EXPECT_NE(static_cast(0), ptr); // Now, we have an allocation: EXPECT_FALSE(segment_->allMemoryDeallocated()); // deallocate it; it shouldn't cause disruption. segment_->deallocate(ptr, 1024); EXPECT_TRUE(segment_->allMemoryDeallocated()); // re-open it. segment_.reset(new MemorySegmentMapped(mapped_file, false)); } } TEST_F(MemorySegmentMappedTest, createWithSize) { boost::interprocess::file_mapping::remove(mapped_file); // Re-create the mapped file with a non-default initial size, and confirm // the size is actually the specified one. segment_.reset(new MemorySegmentMapped(mapped_file, true, 64 * 1024)); EXPECT_NE(DEFAULT_INITIAL_SIZE, 64 * 1024); EXPECT_EQ(64 * 1024, segment_->getSize()); } TEST_F(MemorySegmentMappedTest, openFail) { // The given file is directory EXPECT_THROW(MemorySegmentMapped("/", true), MemorySegmentOpenError); // file doesn't exist and directory isn't writable (we assume the root // directory is not writable for the user running the test). EXPECT_THROW(MemorySegmentMapped("/test.mapped", true), MemorySegmentOpenError); // file doesn't exist and it's read-only (so open-only) EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped"), MemorySegmentOpenError); // Likewise. read-write mode but creation is suppressed. EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped", false), MemorySegmentOpenError); } TEST_F(MemorySegmentMappedTest, allocate) { // Various case of allocation. The simplest cases are covered above. // (Clearly) exceeding the available size, which should cause growing // the segment EXPECT_THROW(segment_->allocate(DEFAULT_INITIAL_SIZE + 1), MemorySegmentGrown); // The size should have been doubled. EXPECT_EQ(DEFAULT_INITIAL_SIZE * 2, segment_->getSize()); // In this case it should now succeed. void* ptr = segment_->allocate(DEFAULT_INITIAL_SIZE + 1); EXPECT_NE(static_cast(0), ptr); EXPECT_FALSE(segment_->allMemoryDeallocated()); // Same set of checks, but for a larger size. EXPECT_THROW(segment_->allocate(DEFAULT_INITIAL_SIZE * 10), MemorySegmentGrown); // the segment should have grown to the minimum size that could allocate // the given size of memory. EXPECT_EQ(DEFAULT_INITIAL_SIZE * 16, segment_->getSize()); // And allocate() should now succeed. ptr = segment_->allocate(DEFAULT_INITIAL_SIZE * 10); EXPECT_NE(static_cast(0), ptr); // (we'll left the regions created in the file there; the entire file // will be removed at the end of the test) } TEST_F(MemorySegmentMappedTest, badAllocate) { // Make the mapped file non-writable; managed_mapped_file::grow() will // fail, resulting in std::bad_alloc const std::string chmod_cmd = "chmod 444 " + std::string(mapped_file); std::system(chmod_cmd.c_str()); EXPECT_THROW(segment_->allocate(DEFAULT_INITIAL_SIZE * 2), std::bad_alloc); } // XXX: this test can cause too strong side effect (creating a very large // file), so we disable it by default TEST_F(MemorySegmentMappedTest, DISABLED_allocateHuge) { EXPECT_THROW(segment_->allocate(std::numeric_limits::max()), std::bad_alloc); } TEST_F(MemorySegmentMappedTest, badDeallocate) { void* ptr = segment_->allocate(4); EXPECT_NE(static_cast(0), ptr); segment_->deallocate(ptr, 4); // this is okay // This is duplicate dealloc; should trigger assertion failure. EXPECT_DEATH_IF_SUPPORTED({segment_->deallocate(ptr, 4);}, ""); resetSegment(); // the segment is possibly broken; reset it. // Deallocating at an invalid address; this would result in crash (the // behavior may not be portable enough; if so we should disable it by // default). ptr = segment_->allocate(4); EXPECT_NE(static_cast(0), ptr); EXPECT_DEATH_IF_SUPPORTED({ segment_->deallocate(static_cast(ptr) + 1, 3); }, ""); resetSegment(); // Invalid size; this implementation doesn't detect such errors. ptr = segment_->allocate(4); EXPECT_NE(static_cast(0), ptr); segment_->deallocate(ptr, 8); EXPECT_TRUE(segment_->allMemoryDeallocated()); } /* TEST(MemorySegmentLocal, TestNullDeallocate) { auto_ptr segment(new MemorySegmentLocal()); // By default, nothing is allocated. EXPECT_TRUE(segment->allMemoryDeallocated()); // NULL deallocation is a no-op. EXPECT_NO_THROW(segment->deallocate(NULL, 1024)); // This should still return true. EXPECT_TRUE(segment->allMemoryDeallocated()); } */ }