// 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 <util/tests/memory_segment_common_unittest.h>
#include <util/unittests/check_valgrind.h>
#include <util/unittests/interprocess_util.h>

#include <util/memory_segment_mapped.h>
#include <exceptions/exceptions.h>

#include <gtest/gtest.h>

#include <boost/interprocess/file_mapping.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/foreach.hpp>

#include <stdint.h>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <fstream>
#include <string>
#include <vector>
#include <map>

#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>

using namespace isc::util;
using boost::scoped_ptr;
using isc::util::unittests::parentReadState;

namespace {
// Shortcut to keep code shorter
const MemorySegmentMapped::OpenMode OPEN_FOR_WRITE =
    MemorySegmentMapped::OPEN_FOR_WRITE;
const MemorySegmentMapped::OpenMode OPEN_OR_CREATE =
    MemorySegmentMapped::OPEN_OR_CREATE;
const MemorySegmentMapped::OpenMode CREATE_ONLY =
    MemorySegmentMapped::CREATE_ONLY;

const char* const mapped_file = TEST_DATA_BUILDDIR "/test.mapped";
const size_t DEFAULT_INITIAL_SIZE = 32 * 1024; // intentionally hardcoded

// A simple RAII-style wrapper for a pipe.  Several tests in this file use
// pipes, so this helper will be useful.
class PipeHolder {
public:
    PipeHolder() {
        if (pipe(fds_) == -1) {
            isc_throw(isc::Unexpected, "pipe failed");
        }
    }
    ~PipeHolder() {
        close(fds_[0]);
        close(fds_[1]);
    }
    int getReadFD() const { return (fds_[0]); }
    int getWriteFD() const { return (fds_[1]); }
private:
    int fds_[2];
};

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, OPEN_OR_CREATE));
    }

    scoped_ptr<MemorySegmentMapped> segment_;
};

TEST(MemorySegmentMappedConstantTest, staticVariables) {
    // Attempt to take address of MemorySegmentMapped::INITIAL_SIZE.
    // It helps in case we accidentally remove the definition from the main
    // code.
    EXPECT_EQ(DEFAULT_INITIAL_SIZE, *(&MemorySegmentMapped::INITIAL_SIZE));
}

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<void*>(NULL), 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 in read-write mode, but don't try to create it
        // this time.
        segment_.reset();       // make sure close is first.
        segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_FOR_WRITE));
    }
}

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.
    const size_t new_size = 64 * 1024;
    EXPECT_NE(new_size, segment_->getSize());
    segment_.reset();
    segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE,
                                           new_size));
    EXPECT_EQ(new_size, segment_->getSize());
}

TEST_F(MemorySegmentMappedTest, createOnly) {
    // First, allocate some data in the existing segment
    EXPECT_TRUE(segment_->allocate(16));
    // Close it, and then open it again in the create-only mode.  the existing
    // file should be internally removed, and so the resulting segment
    // should be "empty" (all deallocated).
    segment_.reset();
    segment_.reset(new MemorySegmentMapped(mapped_file, CREATE_ONLY));
    EXPECT_TRUE(segment_->allMemoryDeallocated());
}

TEST_F(MemorySegmentMappedTest, openFail) {
    // The given file is directory
    EXPECT_THROW(MemorySegmentMapped("/", OPEN_OR_CREATE),
                 MemorySegmentOpenError);

    // file doesn't exist and directory isn't writable (we assume the
    // following path is not writable for the user running the test).
    EXPECT_THROW(MemorySegmentMapped("/random-glkwjer098/test.mapped",
                                     OPEN_OR_CREATE), MemorySegmentOpenError);

    // It should fail when file doesn't exist and it's read-only (so
    // open-only).
    EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped"),
                 MemorySegmentOpenError);
    // Likewise, it should fail in read-write mode when creation is
    // suppressed.
    EXPECT_THROW(MemorySegmentMapped(TEST_DATA_BUILDDIR "/nosuchfile.mapped",
                                     OPEN_FOR_WRITE), MemorySegmentOpenError);

    // creating with a very small size fails (for sure about 0, and other
    // small values should also make it fail, but it's internal restriction
    // of Boost and cannot be predictable).
    EXPECT_THROW(MemorySegmentMapped(mapped_file, OPEN_OR_CREATE, 0),
                 MemorySegmentOpenError);

    // invalid read-write mode
    EXPECT_THROW(MemorySegmentMapped(
                     mapped_file,
                     static_cast<MemorySegmentMapped::OpenMode>(
                         static_cast<int>(CREATE_ONLY) + 1)),
                 isc::InvalidParameter);

    // Close the existing segment, break its file with bogus data, and
    // try to reopen.  It should fail with exception whether in the
    // read-only or read-write, or "create if not exist" mode.
    segment_.reset();
    std::ofstream ofs(mapped_file, std::ios::trunc);
    ofs << std::string(1024, 'x');
    ofs.close();
    EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file), MemorySegmentOpenError);
    EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file, OPEN_FOR_WRITE),
                 MemorySegmentOpenError);
    EXPECT_THROW(MemorySegmentMapped sgmt(mapped_file, OPEN_OR_CREATE),
                 MemorySegmentOpenError);
}

TEST_F(MemorySegmentMappedTest, allocate) {
    // Various case of allocation.  The simplest cases are covered above.

    // Initially, nothing is allocated.
    EXPECT_TRUE(segment_->allMemoryDeallocated());

    // (Clearly) exceeding the available size, which should cause growing
    // the segment
    const size_t prev_size = segment_->getSize();
    EXPECT_THROW(segment_->allocate(prev_size + 1), MemorySegmentGrown);
    // The size should have been doubled.
    EXPECT_EQ(prev_size * 2, segment_->getSize());
    // But nothing should have been allocated.
    EXPECT_TRUE(segment_->allMemoryDeallocated());

    // Now, the allocation should now succeed.
    void* ptr = segment_->allocate(prev_size + 1);
    EXPECT_NE(static_cast<void*>(NULL), ptr);
    EXPECT_FALSE(segment_->allMemoryDeallocated());

    // Same set of checks, but for a larger size.
    EXPECT_THROW(segment_->allocate(prev_size * 10), MemorySegmentGrown);
    // the segment should have grown to the minimum power-of-2 size that
    // could allocate the given size of memory.
    EXPECT_EQ(prev_size * 16, segment_->getSize());
    // And allocate() should now succeed.
    ptr = segment_->allocate(prev_size * 10);
    EXPECT_NE(static_cast<void*>(NULL), 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) {
    // If the test is run as the root user, the following allocate()
    // call will result in a successful MemorySegmentGrown exception,
    // instead of an abort (due to insufficient permissions during
    // reopen).
    if (getuid() == 0) {
        std::cerr << "Skipping test as it's run as the root user" << std::endl;
        return;
    }

    // Make the mapped file non-writable; managed_mapped_file::grow() will
    // fail, resulting in abort.
    const int ret = chmod(mapped_file, 0444);
    ASSERT_EQ(0, ret);

    if (!isc::util::unittests::runningOnValgrind()) {
        EXPECT_DEATH_IF_SUPPORTED(
            {segment_->allocate(DEFAULT_INITIAL_SIZE * 2);}, "");
    }
}

// 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<size_t>::max()),
                 std::bad_alloc);
}

TEST_F(MemorySegmentMappedTest, badDeallocate) {
    void* ptr = segment_->allocate(4);
    EXPECT_NE(static_cast<void*>(NULL), ptr);

    segment_->deallocate(ptr, 4); // this is okay
    // This is duplicate dealloc; should trigger assertion failure.
    if (!isc::util::unittests::runningOnValgrind()) {
        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).
    if (!isc::util::unittests::runningOnValgrind()) {
        ptr = segment_->allocate(4);
        EXPECT_NE(static_cast<void*>(NULL), ptr);
        EXPECT_DEATH_IF_SUPPORTED({
                segment_->deallocate(static_cast<char*>(ptr) + 1, 3);
            }, "");
        resetSegment();
    }

    // Invalid size; this implementation doesn't detect such errors.
    ptr = segment_->allocate(4);
    EXPECT_NE(static_cast<void*>(NULL), ptr);
    segment_->deallocate(ptr, 8);
    EXPECT_TRUE(segment_->allMemoryDeallocated());
}

// A helper of namedAddress.
void
checkNamedData(const std::string& name, const std::vector<uint8_t>& data,
               MemorySegment& sgmt, bool delete_after_check = false)
{
    const MemorySegment::NamedAddressResult result =
        sgmt.getNamedAddress(name.c_str());
    ASSERT_TRUE(result.first);
    ASSERT_TRUE(result.second);
    EXPECT_EQ(0, std::memcmp(result.second, &data[0], data.size()));

    if (delete_after_check) {
        sgmt.deallocate(result.second, data.size());
        sgmt.clearNamedAddress(name.c_str());
    }
}

TEST_F(MemorySegmentMappedTest, namedAddress) {
    // common test cases
    isc::util::test::checkSegmentNamedAddress(*segment_, false);

    // Set it again and read it in the read-only mode.
    void* ptr16 = segment_->allocate(sizeof(uint16_t));
    const uint16_t test_val16 = 42000;
    *static_cast<uint16_t*>(ptr16) = test_val16;
    EXPECT_FALSE(segment_->setNamedAddress("test address", ptr16));
    segment_.reset();           // close it before opening another one

    segment_.reset(new MemorySegmentMapped(mapped_file));
    MemorySegment::NamedAddressResult result =
        segment_->getNamedAddress("test address");
    ASSERT_TRUE(result.first);
    EXPECT_EQ(test_val16, *static_cast<const uint16_t*>(result.second));

    // try to set an unusually long name.  We re-create the file so
    // creating the name would cause allocation failure and trigger internal
    // segment extension.
    segment_.reset();
    boost::interprocess::file_mapping::remove(mapped_file);
    segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE, 1024));
    const std::string long_name(1025, 'x'); // definitely larger than segment
    // setNamedAddress should return true, indicating segment has grown.
    EXPECT_TRUE(segment_->setNamedAddress(long_name.c_str(), NULL));
    result = segment_->getNamedAddress(long_name.c_str());
    EXPECT_TRUE(result.first);
    EXPECT_FALSE(result.second);

    // Check contents pointed by named addresses survive growing and
    // shrinking segment.
    segment_.reset();
    boost::interprocess::file_mapping::remove(mapped_file);
    segment_.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));

    typedef std::map<std::string, std::vector<uint8_t> > TestData;

    TestData data_list;
    data_list["data1"] =
        std::vector<uint8_t>(80); // arbitrarily chosen small data
    data_list["data2"] =
        std::vector<uint8_t>(5000); // larger than usual segment size
    data_list["data3"] =
        std::vector<uint8_t>(65535); // bigger than most usual data
    bool grown = false;

    // Allocate memory and store data
    for (TestData::iterator it = data_list.begin(); it != data_list.end();
         ++it)
    {
        std::vector<uint8_t>& data = it->second;
        for (int i = 0; i < data.size(); ++i) {
            data[i] = i;
        }
        void *dp = NULL;
        while (!dp) {
            try {
                dp = segment_->allocate(data.size());
                std::memcpy(dp, &data[0], data.size());
                segment_->setNamedAddress(it->first.c_str(), dp);
            } catch (const MemorySegmentGrown&) {
                grown = true;
            }
        }
    }
    // Confirm there's at least one segment extension
    EXPECT_TRUE(grown);
    // Check named data are still valid
    for (TestData::iterator it = data_list.begin(); it != data_list.end();
         ++it)
    {
        checkNamedData(it->first, it->second, *segment_);
    }

    // Confirm they are still valid, while we shrink the segment.  We'll
    // intentionally delete bigger data first so it'll be more likely that
    // shrink has some real effect.
    const char* const names[] = { "data3", "data2", "data1", NULL };
    for (int i = 0; names[i]; ++i) {
        checkNamedData(names[i], data_list[names[i]], *segment_, true);
        segment_->shrinkToFit();
    }
}

TEST_F(MemorySegmentMappedTest, multiProcess) {
    // Test using fork() doesn't work well on valgrind
    if (isc::util::unittests::runningOnValgrind()) {
        return;
    }

    // allocate some data and name its address
    void* ptr = segment_->allocate(sizeof(uint32_t));
    *static_cast<uint32_t*>(ptr) = 424242;
    segment_->setNamedAddress("test address", ptr);

    // close the read-write segment at this point.  our intended use case is
    // to have one or more reader process or at most one exclusive writer
    // process.  so we don't mix reader and writer.
    segment_.reset();

    // Spawn another process and have it open and read the same data.
    PipeHolder pipe_to_child;
    PipeHolder pipe_to_parent;
    const pid_t child_pid = fork();
    ASSERT_NE(-1, child_pid);
    if (child_pid == 0) {
        // child: wait until the parent has opened the read-only segment.
        char from_parent;
        EXPECT_EQ(1, read(pipe_to_child.getReadFD(), &from_parent,
                          sizeof(from_parent)));
        EXPECT_EQ(0, from_parent);

        MemorySegmentMapped sgmt(mapped_file);
        const MemorySegment::NamedAddressResult result =
            sgmt.getNamedAddress("test address");
        ASSERT_TRUE(result.first);
        EXPECT_TRUE(result.second);
        if (result.second) {
            const uint32_t val = *static_cast<const uint32_t*>(result.second);
            EXPECT_EQ(424242, val);
            // tell the parent whether it succeeded. 0 means it did,
            // 0xff means it failed.
            const char ok = (val == 424242) ? 0 : 0xff;
            EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ok, sizeof(ok)));
        }
        exit(0);
    }
    // parent: open another read-only segment, then tell the child to open
    // its own segment.
    segment_.reset(new MemorySegmentMapped(mapped_file));
    const MemorySegment::NamedAddressResult result =
        segment_->getNamedAddress("test address");
    ASSERT_TRUE(result.first);
    ASSERT_TRUE(result.second);
    EXPECT_EQ(424242, *static_cast<const uint32_t*>(result.second));
    const char some_data = 0;
    EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
                       sizeof(some_data)));

    // wait for the completion of the child and checks the result.
    EXPECT_EQ(0, parentReadState(pipe_to_parent.getReadFD()));
}

TEST_F(MemorySegmentMappedTest, nullDeallocate) {
    // NULL deallocation is a no-op.
    EXPECT_NO_THROW(segment_->deallocate(0, 1024));
    EXPECT_TRUE(segment_->allMemoryDeallocated());
}

TEST_F(MemorySegmentMappedTest, shrink) {
    segment_->shrinkToFit();
    // Normally we should be able to expect that the resulting size is
    // smaller than the initial default size. But it's not really
    // guaranteed by the API, so we may have to disable this check (or
    // use EXPECT_GE).
    const size_t shrinked_size = segment_->getSize();
    EXPECT_GT(DEFAULT_INITIAL_SIZE, shrinked_size);

    // Another shrink shouldn't cause disruption.  We expect the size is
    // the same so we confirm it.  The underlying library doesn't guarantee
    // that, so we may have to change it to EXPECT_GE if the test fails
    // on that (MemorySegmentMapped class doesn't rely on this expectation,
    // so it's okay even if it does not always hold).
    segment_->shrinkToFit();
    EXPECT_EQ(shrinked_size, segment_->getSize());

    // Check that the segment is still usable after shrink.
    void *p = NULL;
    while (!p) {
        try {
            p = segment_->allocate(sizeof(uint32_t));
        } catch (const MemorySegmentGrown&) {
            // Do nothing. Just try again.
        }
    }
    segment_->deallocate(p, sizeof(uint32_t));
}

TEST_F(MemorySegmentMappedTest, violateReadOnly) {
    // Create a named address for the tests below, then reset the writer
    // segment so that it won't fail for different reason (i.e., read-write
    // conflict).
    void* ptr = segment_->allocate(sizeof(uint32_t));
    segment_->setNamedAddress("test address", ptr);
    segment_.reset();

    // Attempts to modify memory from the read-only segment directly
    // will result in a crash.
    if (!isc::util::unittests::runningOnValgrind()) {
        EXPECT_DEATH_IF_SUPPORTED({
                MemorySegmentMapped segment_ro(mapped_file);
                const MemorySegment::NamedAddressResult result =
                    segment_ro.getNamedAddress("test address");
                ASSERT_TRUE(result.first);
                ASSERT_TRUE(result.second);
                *static_cast<uint32_t*>(result.second) = 0;
            }, "");
    }

    // If the segment is opened in the read-only mode, modification
    // attempts are prohibited. When detectable it must result in an
    // exception.
    MemorySegmentMapped segment_ro(mapped_file);
    const MemorySegment::NamedAddressResult result =
        segment_ro.getNamedAddress("test address");
    ASSERT_TRUE(result.first);
    EXPECT_NE(static_cast<void*>(NULL), result.second);

    EXPECT_THROW(segment_ro.deallocate(result.second, 4), MemorySegmentError);

    EXPECT_THROW(segment_ro.allocate(16), MemorySegmentError);
    // allocation that would otherwise require growing the segment; permission
    // check should be performed before that.
    EXPECT_THROW(segment_ro.allocate(DEFAULT_INITIAL_SIZE * 2),
                 MemorySegmentError);
    EXPECT_THROW(segment_ro.setNamedAddress("test", NULL), MemorySegmentError);
    EXPECT_THROW(segment_ro.clearNamedAddress("test"), MemorySegmentError);
    EXPECT_THROW(segment_ro.shrinkToFit(), MemorySegmentError);
}

TEST_F(MemorySegmentMappedTest, getCheckSum) {
    const size_t old_cksum = segment_->getCheckSum();

    // We assume the initial segment size is sufficiently larger than
    // the page size.  We'll allocate memory of the page size, and
    // increment all bytes in that page by one.  It will increase our
    // simple checksum value (which just uses the first byte of each
    // page) by one, too.
    const size_t page_sz = boost::interprocess::mapped_region::get_page_size();
    uint8_t* cp0 = static_cast<uint8_t*>(segment_->allocate(page_sz));
    for (uint8_t* cp = cp0; cp < cp0 + page_sz; ++cp) {
        ++*cp;
    }

    EXPECT_EQ(old_cksum + 1, segment_->getCheckSum());
}

// Mode of opening segments in the tests below.
enum TestOpenMode {
    READER = 0,
    WRITER_FOR_WRITE,
    WRITER_OPEN_OR_CREATE,
    WRITER_CREATE_ONLY
};

// A shortcut to attempt to open a specified type of segment (generally
// expecting it to fail)
void
setSegment(TestOpenMode mode, scoped_ptr<MemorySegmentMapped>& sgmt_ptr) {
    switch (mode) {
    case READER:
        sgmt_ptr.reset(new MemorySegmentMapped(mapped_file));
        break;
    case WRITER_FOR_WRITE:
        sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, OPEN_FOR_WRITE));
        break;
    case WRITER_OPEN_OR_CREATE:
        sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, OPEN_OR_CREATE));
        break;
    case WRITER_CREATE_ONLY:
        sgmt_ptr.reset(new MemorySegmentMapped(mapped_file, CREATE_ONLY));
        break;
    }
}

// Common logic for conflictReaderWriter test.  The segment opened in the
// parent process will prevent the segment in the child from being used.
void
conflictCheck(TestOpenMode parent_mode, TestOpenMode child_mode) {
    PipeHolder pipe_to_child;
    PipeHolder pipe_to_parent;
    const pid_t child_pid = fork();
    ASSERT_NE(-1, child_pid);

    if (child_pid == 0) {
        char ch;
        EXPECT_EQ(1, read(pipe_to_child.getReadFD(), &ch, sizeof(ch)));

        ch = 0;                 // 0 = open success, 1 = fail
        try {
            scoped_ptr<MemorySegmentMapped> sgmt;
            setSegment(child_mode, sgmt);
            EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ch, sizeof(ch)));
        } catch (const MemorySegmentOpenError&) {
            ch = 1;
            EXPECT_EQ(1, write(pipe_to_parent.getWriteFD(), &ch, sizeof(ch)));
        }
        exit(0);
    }

    // parent: open a segment, then tell the child to open its own segment of
    // the specified type.
    scoped_ptr<MemorySegmentMapped> sgmt;
    setSegment(parent_mode, sgmt);
    const char some_data = 0;
    EXPECT_EQ(1, write(pipe_to_child.getWriteFD(), &some_data,
                       sizeof(some_data)));

    // wait for the completion of the child and checks the result.  open at
    // the child side should fail, so the parent should get the value of 1.
    EXPECT_EQ(1, parentReadState(pipe_to_parent.getReadFD()));
}

TEST_F(MemorySegmentMappedTest, conflictReaderWriter) {
    // Test using fork() doesn't work well on valgrind
    if (isc::util::unittests::runningOnValgrind()) {
        return;
    }

    // Below, we check all combinations of conflicts between reader and writer
    // will fail.  We first make sure there's no other reader or writer.
    segment_.reset();

    // reader opens segment, then writer (OPEN_FOR_WRITE) tries to open
    conflictCheck(READER, WRITER_FOR_WRITE);
    // reader opens segment, then writer (OPEN_OR_CREATE) tries to open
    conflictCheck(READER, WRITER_OPEN_OR_CREATE);
    // reader opens segment, then writer (CREATE_ONLY) tries to open
    conflictCheck(READER, WRITER_CREATE_ONLY);

    // writer (OPEN_FOR_WRITE) opens a segment, then reader tries to open
    conflictCheck(WRITER_FOR_WRITE, READER);
    // writer (OPEN_OR_CREATE) opens a segment, then reader tries to open
    conflictCheck(WRITER_OPEN_OR_CREATE, READER);
    // writer (CREATE_ONLY) opens a segment, then reader tries to open
    conflictCheck(WRITER_CREATE_ONLY, READER);

    // writer opens segment, then another writer (OPEN_FOR_WRITE) tries to open
    conflictCheck(WRITER_FOR_WRITE, WRITER_FOR_WRITE);
    // writer opens segment, then another writer (OPEN_OR_CREATE) tries to open
    conflictCheck(WRITER_FOR_WRITE, WRITER_OPEN_OR_CREATE);
    // writer opens segment, then another writer (CREATE_ONLY) tries to open
    conflictCheck(WRITER_FOR_WRITE, WRITER_CREATE_ONLY);
}

}
