/////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009-2014 Alan Wright. All rights reserved.
// Distributable under the terms of either the Apache License (Version 2.0)
// or the GNU Lesser General Public License.
/////////////////////////////////////////////////////////////////////////////

#include "TestInc.h"
#include "LuceneTestFixture.h"
#include "TestUtils.h"
#include "IndexReader.h"
#include "IndexWriter.h"
#include "WhitespaceAnalyzer.h"
#include "Term.h"
#include "MockRAMDirectory.h"
#include "LogDocMergePolicy.h"
#include "Document.h"
#include "Field.h"
#include "TermEnum.h"
#include "TermPositions.h"
#include "SegmentReader.h"
#include "DirectoryReader.h"
#include "MultiReader.h"
#include "ParallelReader.h"
#include "FilterIndexReader.h"
#include "FSDirectory.h"
#include "KeywordAnalyzer.h"
#include "SerialMergeScheduler.h"
#include "StandardAnalyzer.h"
#include "LuceneThread.h"
#include "IndexSearcher.h"
#include "TermQuery.h"
#include "ScoreDoc.h"
#include "TopDocs.h"
#include "BitVector.h"
#include "IndexDeletionPolicy.h"
#include "Random.h"
#include "MiscUtils.h"
#include "FileUtils.h"

using namespace Lucene;

typedef LuceneTestFixture IndexReaderReopenTest;

namespace TestReopen {

DECLARE_SHARED_PTR(TestableReopen)
DECLARE_SHARED_PTR(ReaderCouple)
DECLARE_SHARED_PTR(ReaderThread)
DECLARE_SHARED_PTR(ReaderThreadTask)

class TestableReopen {
public:
    virtual ~TestableReopen() {
    }

public:
    virtual IndexReaderPtr openReader() = 0;
    virtual void modifyIndex(int32_t i) = 0;
};

class ReaderCouple {
public:
    ReaderCouple(const IndexReaderPtr& r1, const IndexReaderPtr& r2) {
        newReader = r1;
        refreshedReader = r2;
    }

    virtual ~ReaderCouple() {
    }

public:
    IndexReaderPtr newReader;
    IndexReaderPtr refreshedReader;
};

class ReaderThreadTask : public LuceneObject {
public:
    ReaderThreadTask() {
        stopped = false;
    }

    virtual ~ReaderThreadTask() {
    }

    LUCENE_CLASS(ReaderThreadTask);

protected:
    bool stopped;

public:
    void stop() {
        stopped = true;
    }

    virtual void run() = 0;
};

class ReaderThread : public LuceneThread {
public:
    ReaderThread(const ReaderThreadTaskPtr& task) {
        this->task = task;
    }

    virtual ~ReaderThread() {
    }

    LUCENE_CLASS(ReaderThread);

protected:
    ReaderThreadTaskPtr task;

public:
    void stopThread() {
        task->stop();
    }

    virtual void run() {
        try {
            task->run();
        } catch (LuceneException& e) {
            FAIL() << "Unexpected exception: " << e.getError();
        }
    }
};

}

static DocumentPtr createDocument(int32_t n, int32_t numFields) {
    StringStream sb;
    DocumentPtr doc = newLucene<Document>();
    sb << L"a" << n;
    doc->add(newLucene<Field>(L"field1", sb.str(), Field::STORE_YES, Field::INDEX_ANALYZED));
    doc->add(newLucene<Field>(L"fielda", sb.str(), Field::STORE_YES, Field::INDEX_NOT_ANALYZED_NO_NORMS));
    doc->add(newLucene<Field>(L"fieldb", sb.str(), Field::STORE_YES, Field::INDEX_NO));
    sb << L" b" << n;
    for (int32_t i = 1; i < numFields; ++i) {
        doc->add(newLucene<Field>(L"field" + StringUtils::toString(i + 1), sb.str(), Field::STORE_YES, Field::INDEX_ANALYZED));
    }
    return doc;
}

static void createIndex(const DirectoryPtr& dir, bool multiSegment) {
    IndexWriter::unlock(dir);
    IndexWriterPtr w = newLucene<IndexWriter>(dir, newLucene<WhitespaceAnalyzer>(), IndexWriter::MaxFieldLengthLIMITED);

    w->setMergePolicy(newLucene<LogDocMergePolicy>(w));

    for (int32_t i = 0; i < 100; ++i) {
        w->addDocument(createDocument(i, 4));
        if (multiSegment && (i % 10) == 0) {
            w->commit();
        }
    }

    if (!multiSegment) {
        w->optimize();
    }

    w->close();

    IndexReaderPtr r = IndexReader::open(dir, false);
    if (multiSegment) {
        EXPECT_TRUE(r->getSequentialSubReaders().size() > 1);
    } else {
        EXPECT_EQ(r->getSequentialSubReaders().size(), 1);
    }
    r->close();
}

static void _modifyIndex(int32_t i, const DirectoryPtr& dir) {
    switch (i) {
    case 0: {
        IndexWriterPtr w = newLucene<IndexWriter>(dir, newLucene<WhitespaceAnalyzer>(), IndexWriter::MaxFieldLengthLIMITED);
        w->deleteDocuments(newLucene<Term>(L"field2", L"a11"));
        w->deleteDocuments(newLucene<Term>(L"field2", L"b30"));
        w->close();
        break;
    }
    case 1: {
        IndexReaderPtr reader = IndexReader::open(dir, false);
        reader->setNorm(4, L"field1", (uint8_t)123);
        reader->setNorm(44, L"field2", (uint8_t)222);
        reader->setNorm(44, L"field4", (uint8_t)22);
        reader->close();
        break;
    }
    case 2: {
        IndexWriterPtr w = newLucene<IndexWriter>(dir, newLucene<WhitespaceAnalyzer>(), IndexWriter::MaxFieldLengthLIMITED);
        w->optimize();
        w->close();
        break;
    }
    case 3: {
        IndexWriterPtr w = newLucene<IndexWriter>(dir, newLucene<WhitespaceAnalyzer>(), IndexWriter::MaxFieldLengthLIMITED);
        w->addDocument(createDocument(101, 4));
        w->optimize();
        w->addDocument(createDocument(102, 4));
        w->addDocument(createDocument(103, 4));
        w->close();
        break;
    }
    case 4: {
        IndexReaderPtr reader = IndexReader::open(dir, false);
        reader->setNorm(5, L"field1", (uint8_t)123);
        reader->setNorm(55, L"field2", (uint8_t)222);
        reader->close();
        break;
    }
    case 5: {
        IndexWriterPtr w = newLucene<IndexWriter>(dir, newLucene<WhitespaceAnalyzer>(), IndexWriter::MaxFieldLengthLIMITED);
        w->addDocument(createDocument(101, 4));
        w->close();
        break;
    }
    }
}

static void checkIndexEquals(const IndexReaderPtr& index1, const IndexReaderPtr& index2) {
    EXPECT_EQ(index1->numDocs(), index2->numDocs());
    EXPECT_EQ(index1->maxDoc(), index2->maxDoc());
    EXPECT_EQ(index1->hasDeletions(), index2->hasDeletions());
    EXPECT_EQ(index1->isOptimized(), index2->isOptimized());

    // check field names
    HashSet<String> _fields1 = index1->getFieldNames(IndexReader::FIELD_OPTION_ALL);
    Collection<String> fields1 = Collection<String>::newInstance(_fields1.begin(), _fields1.end());
    std::sort(fields1.begin(), fields1.end());
    HashSet<String> _fields2 = index1->getFieldNames(IndexReader::FIELD_OPTION_ALL);
    Collection<String> fields2 = Collection<String>::newInstance(_fields2.begin(), _fields2.end());
    std::sort(fields2.begin(), fields2.end());
    EXPECT_EQ(fields1.size(), fields2.size());

    for (int32_t i = 0; i < fields1.size(); ++i) {
        EXPECT_EQ(fields1[i], fields2[i]);
    }

    // check norms
    for (int32_t i = 0; i < fields1.size(); ++i) {
        String curField = fields1[i];
        ByteArray norms1 = index1->norms(curField);
        ByteArray norms2 = index2->norms(curField);
        if (norms1 && norms2) {
            EXPECT_TRUE(norms1.equals(norms2));
        } else {
            EXPECT_TRUE(norms1 == norms2);
        }
    }

    // check deletions
    for (int32_t i = 0; i < index1->maxDoc(); ++i) {
        EXPECT_EQ(index1->isDeleted(i), index2->isDeleted(i));
    }

    // check stored fields
    for (int32_t i = 0; i < index1->maxDoc(); ++i) {
        if (!index1->isDeleted(i)) {
            DocumentPtr doc1 = index1->document(i);
            DocumentPtr doc2 = index2->document(i);
            Collection<FieldablePtr> storedFields1 = doc1->getFields();
            Collection<FieldablePtr> storedFields2 = doc2->getFields();
            EXPECT_EQ(storedFields1.size(), storedFields2.size());
            for (int32_t j = 0; j < storedFields1.size(); ++j) {
                EXPECT_EQ(storedFields1[j]->name(), storedFields2[j]->name());
                EXPECT_EQ(storedFields1[j]->stringValue(), storedFields2[j]->stringValue());
            }
        }
    }

    // check dictionary and posting lists
    TermEnumPtr enum1 = index1->terms();
    TermEnumPtr enum2 = index2->terms();
    TermPositionsPtr tp1 = index1->termPositions();
    TermPositionsPtr tp2 = index2->termPositions();

    while (enum1->next()) {
        EXPECT_TRUE(enum2->next());
        EXPECT_TRUE(enum1->term()->equals(enum2->term()));
        tp1->seek(enum1->term());
        tp2->seek(enum1->term());
        while (tp1->next()) {
            EXPECT_TRUE(tp2->next());
            EXPECT_EQ(tp1->doc(), tp2->doc());
            EXPECT_EQ(tp1->freq(), tp2->freq());
            for (int32_t i = 0; i < tp1->freq(); ++i) {
                EXPECT_EQ(tp1->nextPosition(), tp2->nextPosition());
            }
        }
    }
}

static void checkReaderClosed(const IndexReaderPtr& reader, bool checkSubReaders, bool checkNormsClosed) {
    EXPECT_EQ(0, reader->getRefCount());

    if (checkNormsClosed && MiscUtils::typeOf<SegmentReader>(reader)) {
        EXPECT_TRUE(boost::dynamic_pointer_cast<SegmentReader>(reader)->normsClosed());
    }

    if (checkSubReaders) {
        if (MiscUtils::typeOf<DirectoryReader>(reader)) {
            Collection<IndexReaderPtr> subReaders = reader->getSequentialSubReaders();
            for (int32_t i = 0; i < subReaders.size(); ++i) {
                checkReaderClosed(subReaders[i], checkSubReaders, checkNormsClosed);
            }
        }

        if (MiscUtils::typeOf<MultiReader>(reader)) {
            Collection<IndexReaderPtr> subReaders = reader->getSequentialSubReaders();
            for (int32_t i = 0; i < subReaders.size(); ++i) {
                checkReaderClosed(subReaders[i], checkSubReaders, checkNormsClosed);
            }
        }

        if (MiscUtils::typeOf<ParallelReader>(reader)) {
            Collection<IndexReaderPtr> subReaders = boost::dynamic_pointer_cast<ParallelReader>(reader)->getSubReaders();
            for (int32_t i = 0; i < subReaders.size(); ++i) {
                checkReaderClosed(subReaders[i], checkSubReaders, checkNormsClosed);
            }
        }
    }
}

static TestReopen::ReaderCouplePtr refreshReader(const IndexReaderPtr& reader, const TestReopen::TestableReopenPtr& test, int32_t modify, bool hasChanges) {
    static SynchronizePtr createReaderMutex = newInstance<Synchronize>();
    SyncLock readersLock(createReaderMutex);
    IndexReaderPtr r;
    if (test) {
        test->modifyIndex(modify);
        r = test->openReader();
    }

    IndexReaderPtr refreshed;
    LuceneException finally;
    try {
        refreshed = reader->reopen();
    } catch (LuceneException& e) {
        finally = e;
    }
    if (!refreshed && r) {
        // Hit exception - close opened reader
        r->close();
    }
    finally.throwException();

    if (hasChanges) {
        if (refreshed == reader) {
            boost::throw_exception(IllegalArgumentException(L"No new IndexReader instance created during refresh."));
        }
    } else {
        if (refreshed != reader) {
            boost::throw_exception(IllegalArgumentException(L"New IndexReader instance created during refresh even though index had no changes."));
        }
    }

    return newInstance<TestReopen::ReaderCouple>(r, refreshed);
}

static TestReopen::ReaderCouplePtr refreshReader(const IndexReaderPtr& reader, bool hasChanges) {
    return refreshReader(reader, TestReopen::TestableReopenPtr(), -1, hasChanges);
}

static void performDefaultTests(const TestReopen::TestableReopenPtr& test) {
    IndexReaderPtr index1 = test->openReader();
    IndexReaderPtr index2 = test->openReader();

    checkIndexEquals(index1, index2);

    // verify that reopen() does not return a new reader instance in case the index has no changes
    TestReopen::ReaderCouplePtr couple = refreshReader(index2, false);
    EXPECT_EQ(couple->refreshedReader, index2);

    couple = refreshReader(index2, test, 0, true);
    index1->close();
    index1 = couple->newReader;

    IndexReaderPtr index2_refreshed = couple->refreshedReader;
    index2->close();

    // test if refreshed reader and newly opened reader return equal results
    checkIndexEquals(index1, index2_refreshed);

    index2_refreshed->close();
    checkReaderClosed(index2, true, true);
    checkReaderClosed(index2_refreshed, true, true);

    index2 = test->openReader();

    for (int32_t i = 1; i < 4; ++i) {
        index1->close();
        couple = refreshReader(index2, test, i, true);
        // refresh IndexReader
        index2->close();

        index2 = couple->refreshedReader;
        index1 = couple->newReader;
        checkIndexEquals(index1, index2);
    }

    index1->close();
    index2->close();
    checkReaderClosed(index1, true, true);
    checkReaderClosed(index2, true, true);
}

static void performTestsWithExceptionInReopen(const TestReopen::TestableReopenPtr& test) {
    IndexReaderPtr index1 = test->openReader();
    IndexReaderPtr index2 = test->openReader();

    checkIndexEquals(index1, index2);

    try {
        refreshReader(index1, test, 0, true);
    } catch (LuceneException& e) {
        EXPECT_TRUE(check_exception(LuceneException::Null)(e));
    }

    // index2 should still be usable and unaffected by the failed reopen() call
    checkIndexEquals(index1, index2);

    index1->close();
    index2->close();
}

static void checkRefCountEquals(int32_t refCount, const IndexReaderPtr& reader) {
    EXPECT_EQ(refCount, reader->getRefCount());
}

namespace TestReopen {

class BasicReopen : public TestableReopen {
public:
    BasicReopen(const DirectoryPtr& dir) {
        this->dir = dir;
    }

    virtual ~BasicReopen() {
    }

protected:
    DirectoryPtr dir;

public:
    virtual IndexReaderPtr openReader() {
        return IndexReader::open(dir, false);
    }

    virtual void modifyIndex(int32_t i) {
        _modifyIndex(i, dir);
    }
};

}

TEST_F(IndexReaderReopenTest, testReopen) {
    DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
    createIndex(dir1, false);
    performDefaultTests(newInstance<TestReopen::BasicReopen>(dir1));
    dir1->close();

    DirectoryPtr dir2 = newLucene<MockRAMDirectory>();
    createIndex(dir2, true);
    performDefaultTests(newInstance<TestReopen::BasicReopen>(dir2));
    dir2->close();
}

namespace TestParallelReaderReopen {

class FirstReopen : public TestReopen::TestableReopen {
public:
    FirstReopen(const DirectoryPtr& dir1, const DirectoryPtr& dir2) {
        this->dir1 = dir1;
        this->dir2 = dir2;
    }

    virtual ~FirstReopen() {
    }

protected:
    DirectoryPtr dir1;
    DirectoryPtr dir2;

public:
    virtual IndexReaderPtr openReader() {
        ParallelReaderPtr pr = newLucene<ParallelReader>();
        pr->add(IndexReader::open(dir1, false));
        pr->add(IndexReader::open(dir2, false));
        return pr;
    }

    virtual void modifyIndex(int32_t i) {
        _modifyIndex(i, dir1);
        _modifyIndex(i, dir2);
    }
};

class SecondReopen : public TestReopen::TestableReopen {
public:
    SecondReopen(const DirectoryPtr& dir3, const DirectoryPtr& dir4) {
        this->dir3 = dir3;
        this->dir4 = dir4;
    }

    virtual ~SecondReopen() {
    }

protected:
    DirectoryPtr dir3;
    DirectoryPtr dir4;

public:
    virtual IndexReaderPtr openReader() {
        ParallelReaderPtr pr = newLucene<ParallelReader>();
        pr->add(IndexReader::open(dir3, false));
        pr->add(IndexReader::open(dir4, false));
        // Does not implement reopen, so hits exception
        pr->add(newLucene<FilterIndexReader>(IndexReader::open(dir3, false)));
        return pr;
    }

    virtual void modifyIndex(int32_t i) {
        _modifyIndex(i, dir3);
        _modifyIndex(i, dir4);
    }
};

}

TEST_F(IndexReaderReopenTest, testParallelReaderReopen) {
    DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
    createIndex(dir1, true);
    DirectoryPtr dir2 = newLucene<MockRAMDirectory>();
    createIndex(dir2, true);

    performDefaultTests(newInstance<TestParallelReaderReopen::FirstReopen>(dir1, dir2));

    dir1->close();
    dir2->close();

    DirectoryPtr dir3 = newLucene<MockRAMDirectory>();
    createIndex(dir3, true);
    DirectoryPtr dir4 = newLucene<MockRAMDirectory>();
    createIndex(dir4, true);

    performTestsWithExceptionInReopen(newInstance<TestParallelReaderReopen::SecondReopen>(dir3, dir4));

    dir3->close();
    dir4->close();
}

static void doTestReopenWithCommit(const DirectoryPtr& dir, bool withReopen) {
    IndexWriterPtr iwriter = newLucene<IndexWriter>(dir, newLucene<KeywordAnalyzer>(), true, IndexWriter::MaxFieldLengthLIMITED);
    iwriter->setMergeScheduler(newLucene<SerialMergeScheduler>());
    IndexReaderPtr reader = IndexReader::open(dir, false);

    LuceneException finally;
    try {
        int32_t M = 3;
        for (int32_t i = 0; i < 4; ++i) {
            for (int32_t j = 0; j < M; ++j) {
                DocumentPtr doc = newLucene<Document>();
                doc->add(newLucene<Field>(L"id", StringUtils::toString(i) + L"_" + StringUtils::toString(j), Field::STORE_YES, Field::INDEX_NOT_ANALYZED));
                doc->add(newLucene<Field>(L"id2", StringUtils::toString(i) + L"_" + StringUtils::toString(j), Field::STORE_YES, Field::INDEX_NOT_ANALYZED_NO_NORMS));
                doc->add(newLucene<Field>(L"id3", StringUtils::toString(i) + L"_" + StringUtils::toString(j), Field::STORE_YES, Field::INDEX_NO));
                iwriter->addDocument(doc);
                if (i > 0) {
                    int32_t k = i - 1;
                    int32_t n = j + k * M;
                    DocumentPtr prevItereationDoc = reader->document(n);
                    EXPECT_TRUE(prevItereationDoc);
                    String id = prevItereationDoc->get(L"id");
                    EXPECT_EQ(StringUtils::toString(k) + L"_" + StringUtils::toString(j), id);
                }
            }
            iwriter->commit();
            if (withReopen) {
                // reopen
                IndexReaderPtr r2 = reader->reopen();
                if (reader != r2) {
                    reader->close();
                    reader = r2;
                }
            } else {
                // recreate
                reader->close();
                reader = IndexReader::open(dir, false);
            }
        }
    } catch (LuceneException& e) {
        finally = e;
    }
    iwriter->close();
    reader->close();
    finally.throwException();
}

/// IndexWriter.commit() does not update the index version populate an index in iterations.
/// At the end of every iteration, commit the index and reopen/recreate the reader.
/// In each iteration verify the work of previous iteration.
/// Try this once with reopen once recreate, on both RAMDir and FSDir.
TEST_F(IndexReaderReopenTest, testCommitReopenFS) {
    String indexDir(FileUtils::joinPath(getTempDir(), L"IndexReaderReopen"));
    DirectoryPtr dir = FSDirectory::open(indexDir);
    doTestReopenWithCommit(dir, true);
    dir->close();
}

TEST_F(IndexReaderReopenTest, testCommitRecreateFS) {
    String indexDir(FileUtils::joinPath(getTempDir(), L"IndexReaderReopen"));
    DirectoryPtr dir = FSDirectory::open(indexDir);
    doTestReopenWithCommit(dir, false);
    dir->close();
}

TEST_F(IndexReaderReopenTest, testCommitRecreateRAM) {
    DirectoryPtr dir = newLucene<MockRAMDirectory>();
    doTestReopenWithCommit(dir, false);
    dir->close();
}

namespace TestMultiReaderReopen {

class FirstReopen : public TestReopen::TestableReopen {
public:
    FirstReopen(const DirectoryPtr& dir1, const DirectoryPtr& dir2) {
        this->dir1 = dir1;
        this->dir2 = dir2;
    }

protected:
    DirectoryPtr dir1;
    DirectoryPtr dir2;

public:
    virtual IndexReaderPtr openReader() {
        Collection<IndexReaderPtr> readers = newCollection<IndexReaderPtr>(IndexReader::open(dir1, false), IndexReader::open(dir2, false));
        return newLucene<MultiReader>(readers);
    }

    virtual void modifyIndex(int32_t i) {
        _modifyIndex(i, dir1);
        _modifyIndex(i, dir2);
    }
};

class SecondReopen : public TestReopen::TestableReopen {
public:
    SecondReopen(const DirectoryPtr& dir3, const DirectoryPtr& dir4) {
        this->dir3 = dir3;
        this->dir4 = dir4;
    }

protected:
    DirectoryPtr dir3;
    DirectoryPtr dir4;

public:
    virtual IndexReaderPtr openReader() {
        Collection<IndexReaderPtr> readers = Collection<IndexReaderPtr>::newInstance(3);
        readers[0] = IndexReader::open(dir3, false);
        readers[1] = IndexReader::open(dir4, false);
        // Does not implement reopen, so hits exception
        readers[2] = newLucene<FilterIndexReader>(IndexReader::open(dir3, false));
        return newLucene<MultiReader>(readers);
    }

    virtual void modifyIndex(int32_t i) {
        _modifyIndex(i, dir3);
        _modifyIndex(i, dir4);
    }
};

}

TEST_F(IndexReaderReopenTest, testMultiReaderReopen) {
    DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
    createIndex(dir1, true);
    DirectoryPtr dir2 = newLucene<MockRAMDirectory>();
    createIndex(dir2, true);

    performDefaultTests(newInstance<TestMultiReaderReopen::FirstReopen>(dir1, dir2));

    dir1->close();
    dir2->close();

    DirectoryPtr dir3 = newLucene<MockRAMDirectory>();
    createIndex(dir3, true);
    DirectoryPtr dir4 = newLucene<MockRAMDirectory>();
    createIndex(dir4, true);

    performTestsWithExceptionInReopen(newInstance<TestMultiReaderReopen::SecondReopen>(dir3, dir4));

    dir3->close();
    dir4->close();
}

namespace TestMixedReaders {

class MixedReopen : public TestReopen::TestableReopen {
public:
    MixedReopen(const DirectoryPtr& dir1, const DirectoryPtr& dir2, const DirectoryPtr& dir3, const DirectoryPtr& dir4, const DirectoryPtr& dir5) {
        this->dir1 = dir1;
        this->dir2 = dir2;
        this->dir3 = dir3;
        this->dir4 = dir4;
        this->dir5 = dir5;
    }

protected:
    DirectoryPtr dir1;
    DirectoryPtr dir2;
    DirectoryPtr dir3;
    DirectoryPtr dir4;
    DirectoryPtr dir5;

public:
    virtual IndexReaderPtr openReader() {
        ParallelReaderPtr pr = newLucene<ParallelReader>();
        pr->add(IndexReader::open(dir1, false));
        pr->add(IndexReader::open(dir2, false));

        Collection<IndexReaderPtr> readers = newCollection<IndexReaderPtr>(IndexReader::open(dir3, false), IndexReader::open(dir4, false));
        MultiReaderPtr mr = newLucene<MultiReader>(readers);

        Collection<IndexReaderPtr> mixedReaders = newCollection<IndexReaderPtr>(pr, mr, IndexReader::open(dir5, false));
        return newLucene<MultiReader>(mixedReaders);
    }

    virtual void modifyIndex(int32_t i) {
        // only change norms in this index to maintain the same number of docs
        // for each of ParallelReader's subreaders
        if (i == 1) {
            _modifyIndex(i, dir1);
        }
        _modifyIndex(i, dir4);
        _modifyIndex(i, dir5);
    }
};

}

TEST_F(IndexReaderReopenTest, testMixedReaders) {
    DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
    createIndex(dir1, true);
    DirectoryPtr dir2 = newLucene<MockRAMDirectory>();
    createIndex(dir2, true);
    DirectoryPtr dir3 = newLucene<MockRAMDirectory>();
    createIndex(dir3, false);
    DirectoryPtr dir4 = newLucene<MockRAMDirectory>();
    createIndex(dir4, true);
    DirectoryPtr dir5 = newLucene<MockRAMDirectory>();
    createIndex(dir5, false);

    performDefaultTests(newInstance<TestMixedReaders::MixedReopen>(dir1, dir2, dir3, dir4, dir5));

    dir1->close();
    dir2->close();
    dir3->close();
    dir4->close();
    dir5->close();
}

TEST_F(IndexReaderReopenTest, testReferenceCounting) {
    for (int32_t mode = 0; mode < 4; ++mode) {
        DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
        createIndex(dir1, true);

        IndexReaderPtr reader0 = IndexReader::open(dir1, false);
        checkRefCountEquals(1, reader0);

        EXPECT_TRUE(MiscUtils::typeOf<DirectoryReader>(reader0));
        Collection<IndexReaderPtr> subReaders0 = reader0->getSequentialSubReaders();

        for (int32_t i = 0; i < subReaders0.size(); ++i) {
            checkRefCountEquals(1, subReaders0[i]);
        }

        // delete first document, so that only one of the subReaders have to be re-opened
        IndexReaderPtr modifier = IndexReader::open(dir1, false);
        modifier->deleteDocument(0);
        modifier->close();

        IndexReaderPtr reader1 = refreshReader(reader0, true)->refreshedReader;
        EXPECT_TRUE(MiscUtils::typeOf<DirectoryReader>(reader1));
        Collection<IndexReaderPtr> subReaders1 = reader1->getSequentialSubReaders();
        EXPECT_EQ(subReaders0.size(), subReaders1.size());
        for (int32_t i = 0; i < subReaders0.size(); ++i) {
            if (subReaders0[i] != subReaders1[i]) {
                checkRefCountEquals(1, subReaders0[i]);
                checkRefCountEquals(1, subReaders1[i]);
            } else {
                checkRefCountEquals(2, subReaders0[i]);
            }
        }

        // delete first document, so that only one of the subReaders have to be re-opened
        modifier = IndexReader::open(dir1, false);
        modifier->deleteDocument(1);
        modifier->close();

        IndexReaderPtr reader2 = refreshReader(reader1, true)->refreshedReader;
        EXPECT_TRUE(MiscUtils::typeOf<DirectoryReader>(reader2));
        Collection<IndexReaderPtr> subReaders2 = reader2->getSequentialSubReaders();
        EXPECT_EQ(subReaders1.size(), subReaders2.size());

        for (int32_t i = 0; i < subReaders2.size(); ++i) {
            if (subReaders2[i] == subReaders1[i]) {
                if (subReaders1[i] == subReaders0[i]) {
                    checkRefCountEquals(3, subReaders2[i]);
                } else {
                    checkRefCountEquals(2, subReaders2[i]);
                }
            } else {
                checkRefCountEquals(1, subReaders2[i]);
                if (subReaders0[i] == subReaders1[i]) {
                    checkRefCountEquals(2, subReaders2[i]);
                    checkRefCountEquals(2, subReaders0[i]);
                } else {
                    checkRefCountEquals(1, subReaders0[i]);
                    checkRefCountEquals(1, subReaders1[i]);
                }
            }
        }

        IndexReaderPtr reader3 = refreshReader(reader0, true)->refreshedReader;
        EXPECT_TRUE(MiscUtils::typeOf<DirectoryReader>(reader3));
        Collection<IndexReaderPtr> subReaders3 = reader3->getSequentialSubReaders();
        EXPECT_EQ(subReaders3.size(), subReaders0.size());

        // try some permutations
        switch (mode) {
        case 0:
            reader0->close();
            reader1->close();
            reader2->close();
            reader3->close();
            break;
        case 1:
            reader3->close();
            reader2->close();
            reader1->close();
            reader0->close();
            break;
        case 2:
            reader2->close();
            reader3->close();
            reader0->close();
            reader1->close();
            break;
        case 3:
            reader1->close();
            reader3->close();
            reader2->close();
            reader0->close();
            break;
        }

        checkReaderClosed(reader0, true, true);
        checkReaderClosed(reader1, true, true);
        checkReaderClosed(reader2, true, true);
        checkReaderClosed(reader3, true, true);

        dir1->close();
    }
}

TEST_F(IndexReaderReopenTest, testReferenceCountingMultiReader) {
    for (int32_t mode = 0; mode <= 1; ++mode) {
        DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
        createIndex(dir1, false);
        DirectoryPtr dir2 = newLucene<MockRAMDirectory>();
        createIndex(dir2, true);

        IndexReaderPtr reader1 = IndexReader::open(dir1, false);
        checkRefCountEquals(1, reader1);

        IndexReaderPtr initReader2 = IndexReader::open(dir2, false);
        Collection<IndexReaderPtr> readers = newCollection<IndexReaderPtr>(reader1, initReader2);
        IndexReaderPtr multiReader1 = newLucene<MultiReader>(readers, (mode == 0));
        _modifyIndex(0, dir2);
        checkRefCountEquals(1 + mode, reader1);

        IndexReaderPtr multiReader2 = multiReader1->reopen();
        // index1 hasn't changed, so multiReader2 should share reader1 now with multiReader1
        checkRefCountEquals(2 + mode, reader1);

        _modifyIndex(0, dir1);
        IndexReaderPtr reader2 = reader1->reopen();
        checkRefCountEquals(2 + mode, reader1);

        if (mode == 1) {
            initReader2->close();
        }

        _modifyIndex(1, dir1);
        IndexReaderPtr reader3 = reader2->reopen();
        checkRefCountEquals(2 + mode, reader1);
        checkRefCountEquals(1, reader2);

        multiReader1->close();
        checkRefCountEquals(1 + mode, reader1);

        multiReader1->close();
        checkRefCountEquals(1 + mode, reader1);

        if (mode == 1) {
            initReader2->close();
        }

        reader1->close();
        checkRefCountEquals(1, reader1);

        multiReader2->close();
        checkRefCountEquals(0, reader1);

        multiReader2->close();
        checkRefCountEquals(0, reader1);

        reader3->close();
        checkRefCountEquals(0, reader1);
        checkReaderClosed(reader1, true, false);

        reader2->close();
        checkRefCountEquals(0, reader1);
        checkReaderClosed(reader1, true, false);

        reader2->close();
        checkRefCountEquals(0, reader1);

        reader3->close();
        checkRefCountEquals(0, reader1);
        checkReaderClosed(reader1, true, true);
        dir1->close();
        dir2->close();
    }
}

TEST_F(IndexReaderReopenTest, testReferenceCountingParallelReader) {
    for (int32_t mode = 0; mode <= 1; ++mode) {
        DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
        createIndex(dir1, false);
        DirectoryPtr dir2 = newLucene<MockRAMDirectory>();
        createIndex(dir2, true);

        IndexReaderPtr reader1 = IndexReader::open(dir1, false);
        checkRefCountEquals(1, reader1);

        ParallelReaderPtr parallelReader1 = newLucene<ParallelReader>(mode == 0);
        parallelReader1->add(reader1);
        IndexReaderPtr initReader2 = IndexReader::open(dir2, false);
        parallelReader1->add(initReader2);
        _modifyIndex(1, dir2);
        checkRefCountEquals(1 + mode, reader1);

        IndexReaderPtr parallelReader2 = parallelReader1->reopen();
        // index1 hasn't changed, so parallelReader2 should share reader1 now with parallelReader2
        checkRefCountEquals(2 + mode, reader1);

        _modifyIndex(0, dir1);
        _modifyIndex(0, dir2);
        IndexReaderPtr reader2 = reader1->reopen();
        checkRefCountEquals(2 + mode, reader1);

        if (mode == 1) {
            initReader2->close();
        }

        _modifyIndex(4, dir1);
        IndexReaderPtr reader3 = reader2->reopen();
        checkRefCountEquals(2 + mode, reader1);
        checkRefCountEquals(1, reader2);

        parallelReader1->close();
        checkRefCountEquals(1 + mode, reader1);

        parallelReader1->close();
        checkRefCountEquals(1 + mode, reader1);

        if (mode == 1) {
            initReader2->close();
        }

        reader1->close();
        checkRefCountEquals(1, reader1);

        parallelReader2->close();
        checkRefCountEquals(0, reader1);

        parallelReader2->close();
        checkRefCountEquals(0, reader1);

        reader3->close();
        checkRefCountEquals(0, reader1);
        checkReaderClosed(reader1, true, false);

        reader2->close();
        checkRefCountEquals(0, reader1);
        checkReaderClosed(reader1, true, false);

        reader2->close();
        checkRefCountEquals(0, reader1);

        reader3->close();
        checkRefCountEquals(0, reader1);
        checkReaderClosed(reader1, true, true);
        dir1->close();
        dir2->close();
    }
}

TEST_F(IndexReaderReopenTest, testNormsRefCounting) {
    DirectoryPtr dir1 = newLucene<MockRAMDirectory>();
    createIndex(dir1, false);

    IndexReaderPtr reader1 = IndexReader::open(dir1, false);
    SegmentReaderPtr segmentReader1 = SegmentReader::getOnlySegmentReader(reader1);
    IndexReaderPtr modifier = IndexReader::open(dir1, false);
    modifier->deleteDocument(0);
    modifier->close();

    IndexReaderPtr reader2 = reader1->reopen();
    modifier = IndexReader::open(dir1, false);
    modifier->setNorm(1, L"field1", (uint8_t)50);
    modifier->setNorm(1, L"field2", (uint8_t)50);
    modifier->close();

    IndexReaderPtr reader3 = reader2->reopen();
    SegmentReaderPtr segmentReader3 = SegmentReader::getOnlySegmentReader(reader3);
    modifier = IndexReader::open(dir1, false);
    modifier->deleteDocument(2);
    modifier->close();

    IndexReaderPtr reader4 = reader3->reopen();
    modifier = IndexReader::open(dir1, false);
    modifier->deleteDocument(3);
    modifier->close();

    IndexReaderPtr reader5 = reader3->reopen();

    // Now reader2-reader5 references reader1. reader1 and reader2
    // share the same norms. reader3, reader4, reader5 also share norms.
    checkRefCountEquals(1, reader1);
    EXPECT_TRUE(!segmentReader1->normsClosed());

    reader1->close();

    checkRefCountEquals(0, reader1);
    EXPECT_TRUE(!segmentReader1->normsClosed());

    reader2->close();
    checkRefCountEquals(0, reader1);

    // now the norms for field1 and field2 should be closed
    EXPECT_TRUE(segmentReader1->normsClosed(L"field1"));
    EXPECT_TRUE(segmentReader1->normsClosed(L"field2"));

    // but the norms for field3 and field4 should still be open
    EXPECT_TRUE(!segmentReader1->normsClosed(L"field3"));
    EXPECT_TRUE(!segmentReader1->normsClosed(L"field4"));

    reader3->close();
    checkRefCountEquals(0, reader1);
    EXPECT_TRUE(!segmentReader3->normsClosed());
    reader5->close();
    checkRefCountEquals(0, reader1);
    EXPECT_TRUE(!segmentReader3->normsClosed());
    reader4->close();
    checkRefCountEquals(0, reader1);

    // and now all norms that reader1 used should be closed
    EXPECT_TRUE(segmentReader1->normsClosed());

    // now that reader3, reader4 and reader5 are closed, the norms that those three
    // readers shared should be closed as well
    EXPECT_TRUE(segmentReader3->normsClosed());

    dir1->close();
}

namespace TestReopen {

class ThreadReopen : public TestableReopen {
public:
    ThreadReopen(const DirectoryPtr& dir, int32_t n) {
        this->dir = dir;
        this->n = n;
    }

protected:
    DirectoryPtr dir;
    int32_t n;

public:
    virtual IndexReaderPtr openReader() {
        return IndexReader::open(dir, false);
    }

    virtual void modifyIndex(int32_t i) {
        if (i % 3 == 0) {
            IndexReaderPtr modifier = IndexReader::open(dir, false);
            modifier->setNorm(i, L"field1", (uint8_t)50);
            modifier->close();
        } else if (i % 3 == 1) {
            IndexReaderPtr modifier = IndexReader::open(dir, false);
            modifier->deleteDocument(i % modifier->maxDoc());
            modifier->close();
        } else {
            IndexWriterPtr modifier = newLucene<IndexWriter>(dir, newLucene<StandardAnalyzer>(LuceneVersion::LUCENE_CURRENT), IndexWriter::MaxFieldLengthLIMITED);
            modifier->addDocument(createDocument(n + i, 6));
            modifier->close();
        }
    }
};

class FirstThreadTask : public ReaderThreadTask {
public:
    FirstThreadTask(const IndexReaderPtr& r, const TestableReopenPtr& test, int32_t index, HashSet<IndexReaderPtr> readersToClose, Collection<ReaderCouplePtr> readers) {
        this->r = r;
        this->test = test;
        this->index = index;
        this->readersToClose = readersToClose;
        this->readers = readers;
        this->rnd = newLucene<Random>();
    }

    virtual ~FirstThreadTask() {
    }

protected:
    IndexReaderPtr r;
    TestableReopenPtr test;
    int32_t index;
    HashSet<IndexReaderPtr> readersToClose;
    Collection<ReaderCouplePtr> readers;
    RandomPtr rnd;

public:
    virtual void run() {
        while (!stopped) {
            if (index % 2 == 0) {
                // refresh reader synchronized
                ReaderCouplePtr c = refreshReader(r, test, index, true);
                {
                    SyncLock readersLock(&readersToClose);
                    readersToClose.add(c->newReader);
                    readersToClose.add(c->refreshedReader);
                }
                {
                    SyncLock readersLock(&readers);
                    readers.add(c);
                }
                // prevent too many readers
                break;
            } else {
                // not synchronized
                IndexReaderPtr refreshed = r->reopen();

                IndexSearcherPtr searcher = newLucene<IndexSearcher>(refreshed);
                Collection<ScoreDocPtr> hits = searcher->search(newLucene<TermQuery>(newLucene<Term>(L"field1", L"a" + StringUtils::toString(rnd->nextInt(refreshed->maxDoc())))), FilterPtr(), 1000)->scoreDocs;
                if (!hits.empty()) {
                    searcher->doc(hits[0]->doc);
                }

                // r might have changed because this is not a synchronized method.  However we don't want to make it synchronized to test
                // thread-safety of IndexReader.close().  That's why we add refreshed also to readersToClose, because double closing is fine.
                if (refreshed != r) {
                    refreshed->close();
                }

                {
                    SyncLock readersLock(&readersToClose);
                    readersToClose.add(refreshed);
                }
            }
            LuceneThread::threadSleep(1000);
        }
    }
};

class SecondThreadTask : public ReaderThreadTask {
public:
    SecondThreadTask(const IndexReaderPtr& r, const TestableReopenPtr& test, int32_t index, HashSet<IndexReaderPtr> readersToClose, Collection<ReaderCouplePtr> readers) {
        this->r = r;
        this->test = test;
        this->index = index;
        this->readersToClose = readersToClose;
        this->readers = readers;
        this->rnd = newLucene<Random>();
    }

    virtual ~SecondThreadTask() {
    }

protected:
    IndexReaderPtr r;
    TestableReopenPtr test;
    int32_t index;
    HashSet<IndexReaderPtr> readersToClose;
    Collection<ReaderCouplePtr> readers;
    RandomPtr rnd;

public:
    virtual void run() {
        while (!stopped) {
            int32_t numReaders = 0;
            ReaderCouplePtr c;
            {
                SyncLock readersLock(&readers);
                numReaders = readers.size();
                if (numReaders > 0) {
                    c = readers[rnd->nextInt(numReaders)];
                }
            }
            if (c) {
                static SynchronizePtr checkIndexMutex = newInstance<Synchronize>();
                SyncLock readersLock(checkIndexMutex);
                checkIndexEquals(c->newReader, c->refreshedReader);
            }

            LuceneThread::threadSleep(100);
        }
    }
};

}

TEST_F(IndexReaderReopenTest, testThreadSafety) {
    DirectoryPtr dir = newLucene<MockRAMDirectory>();
    int32_t n = 150;

    IndexWriterPtr writer = newLucene<IndexWriter>(dir, newLucene<StandardAnalyzer>(LuceneVersion::LUCENE_CURRENT), IndexWriter::MaxFieldLengthLIMITED);
    for (int32_t i = 0; i < n; ++i) {
        writer->addDocument(createDocument(i, 3));
    }
    writer->optimize();
    writer->close();

    TestReopen::TestableReopenPtr test = newInstance<TestReopen::ThreadReopen>(dir, n);

    Collection<TestReopen::ReaderCouplePtr> readers = Collection<TestReopen::ReaderCouplePtr>::newInstance();
    IndexReaderPtr firstReader = IndexReader::open(dir, false);
    IndexReaderPtr reader = firstReader;
    RandomPtr rnd = newLucene<Random>();

    Collection<TestReopen::ReaderThreadPtr> threads = Collection<TestReopen::ReaderThreadPtr>::newInstance(n);
    HashSet<IndexReaderPtr> readersToClose = HashSet<IndexReaderPtr>::newInstance();

    for (int32_t i = 0; i < n; ++i) {
        if (i % 10 == 0) {
            IndexReaderPtr refreshed = reader->reopen();
            if (refreshed != reader) {
                SyncLock readersLock(&readersToClose);
                readersToClose.add(reader);
            }
            reader = refreshed;
        }
        IndexReaderPtr r = reader;

        int32_t index = i;

        TestReopen::ReaderThreadTaskPtr task;

        if (i < 20 ||( i >= 50 && i < 70) || i > 90) {
            task = newLucene<TestReopen::FirstThreadTask>(r, test, index, readersToClose, readers);
        } else {
            task = newLucene<TestReopen::SecondThreadTask>(r, test, index, readersToClose, readers);
        }

        threads[i] = newLucene<TestReopen::ReaderThread>(task);
        threads[i]->start();
    }

    LuceneThread::threadSleep(15000);

    for (int32_t i = 0; i < n; ++i) {
        if (threads[i]) {
            threads[i]->stopThread();
        }
    }

    for (int32_t i = 0; i < n; ++i) {
        if (threads[i]) {
            threads[i]->join();
        }
    }

    {
        SyncLock readersLock(&readersToClose);
        for (HashSet<IndexReaderPtr>::iterator reader = readersToClose.begin(); reader != readersToClose.end(); ++reader) {
            (*reader)->close();
        }
    }

    firstReader->close();
    reader->close();

    {
        SyncLock readersLock(&readersToClose);
        for (HashSet<IndexReaderPtr>::iterator reader = readersToClose.begin(); reader != readersToClose.end(); ++reader) {
            checkReaderClosed(*reader, true, true);
        }
    }

    checkReaderClosed(reader, true, true);
    checkReaderClosed(firstReader, true, true);

    dir->close();
}

TEST_F(IndexReaderReopenTest, testCloseOrig) {
    DirectoryPtr dir = newLucene<MockRAMDirectory>();
    createIndex(dir, false);
    IndexReaderPtr r1 = IndexReader::open(dir, false);
    IndexReaderPtr r2 = IndexReader::open(dir, false);
    r2->deleteDocument(0);
    r2->close();

    IndexReaderPtr r3 = r1->reopen();
    EXPECT_NE(r1, r3);
    r1->close();

    try {
        r1->document(2);
    } catch (AlreadyClosedException& e) {
        EXPECT_TRUE(check_exception(LuceneException::AlreadyClosed)(e));
    }

    r3->close();
    dir->close();
}

TEST_F(IndexReaderReopenTest, testDeletes) {
    DirectoryPtr dir = newLucene<MockRAMDirectory>();
    createIndex(dir, false); // Create an index with a bunch of docs (1 segment)

    _modifyIndex(0, dir); // Get delete bitVector on 1st segment
    _modifyIndex(5, dir); // Add a doc (2 segments)

    IndexReaderPtr r1 = IndexReader::open(dir, false);

    _modifyIndex(5, dir); // Add another doc (3 segments)

    IndexReaderPtr r2 = r1->reopen();
    EXPECT_NE(r1, r2);

    // Get SRs for the first segment from original
    SegmentReaderPtr sr1 = boost::dynamic_pointer_cast<SegmentReader>(r1->getSequentialSubReaders()[0]);

    // and reopened IRs
    SegmentReaderPtr sr2 = boost::dynamic_pointer_cast<SegmentReader>(r2->getSequentialSubReaders()[0]);

    // At this point they share the same BitVector
    EXPECT_EQ(sr1->deletedDocs, sr2->deletedDocs);

    r2->deleteDocument(0);

    // r1 should not see the delete
    EXPECT_TRUE(!r1->isDeleted(0));

    // Now r2 should have made a private copy of deleted docs:
    EXPECT_NE(sr1->deletedDocs, sr2->deletedDocs);

    r1->close();
    r2->close();
    dir->close();
}

TEST_F(IndexReaderReopenTest, testDeletes2) {
    DirectoryPtr dir = newLucene<MockRAMDirectory>();
    createIndex(dir, false);

    // Get delete bitVector
    _modifyIndex(0, dir);
    IndexReaderPtr r1 = IndexReader::open(dir, false);

    // Add doc
    _modifyIndex(5, dir);

    IndexReaderPtr r2 = r1->reopen();
    EXPECT_NE(r1, r2);

    Collection<IndexReaderPtr> rs2 = r2->getSequentialSubReaders();

    SegmentReaderPtr sr1 = SegmentReader::getOnlySegmentReader(r1);
    SegmentReaderPtr sr2 = boost::dynamic_pointer_cast<SegmentReader>(rs2[0]);

    // At this point they share the same BitVector
    EXPECT_EQ(sr1->deletedDocs, sr2->deletedDocs);
    BitVectorPtr delDocs = sr1->deletedDocs;
    r1->close();

    r2->deleteDocument(0);
    EXPECT_EQ(delDocs, sr2->deletedDocs);
    r2->close();
    dir->close();
}

namespace TestReopen {

class KeepAllCommits : public IndexDeletionPolicy {
public:
    virtual ~KeepAllCommits() {
    }

    LUCENE_CLASS(KeepAllCommits);

public:
    virtual void onInit(Collection<IndexCommitPtr> commits) {
    }

    virtual void onCommit(Collection<IndexCommitPtr> commits) {
    }
};

}

TEST_F(IndexReaderReopenTest, testReopenOnCommit) {
    DirectoryPtr dir = newLucene<MockRAMDirectory>();
    IndexWriterPtr writer = newLucene<IndexWriter>(dir, newLucene<WhitespaceAnalyzer>(), (IndexDeletionPolicyPtr)newLucene<TestReopen::KeepAllCommits>(), IndexWriter::MaxFieldLengthUNLIMITED);
    for (int32_t i = 0; i < 4; ++i) {
        DocumentPtr doc = newLucene<Document>();
        doc->add(newLucene<Field>(L"id", StringUtils::toString(i), Field::STORE_NO, Field::INDEX_NOT_ANALYZED));
        writer->addDocument(doc);
        MapStringString data = MapStringString::newInstance();
        data.put(L"index", StringUtils::toString(i));
        writer->commit(data);
    }
    for (int32_t i = 0; i < 4; ++i) {
        writer->deleteDocuments(newLucene<Term>(L"id", StringUtils::toString(i)));
        MapStringString data = MapStringString::newInstance();
        data.put(L"index", StringUtils::toString(4 + i));
        writer->commit(data);
    }
    writer->close();

    IndexReaderPtr r = IndexReader::open(dir, false);
    EXPECT_EQ(0, r->numDocs());
    EXPECT_EQ(4, r->maxDoc());

    Collection<IndexCommitPtr> commits = IndexReader::listCommits(dir);
    for (Collection<IndexCommitPtr>::iterator commit = commits.begin(); commit != commits.end(); ++commit) {
        IndexReaderPtr r2 = r->reopen(*commit);
        EXPECT_NE(r2, r);

        try {
            r2->deleteDocument(0);
        } catch (UnsupportedOperationException& e) {
            EXPECT_TRUE(check_exception(LuceneException::UnsupportedOperation)(e));
        }

        MapStringString s = (*commit)->getUserData();
        int32_t v = s.empty() ? -1 : StringUtils::toInt(s.get(L"index"));

        if (v < 4) {
            EXPECT_EQ(1 + v, r2->numDocs());
        } else {
            EXPECT_EQ(7 - v, r2->numDocs());
        }

        r->close();
        r = r2;
    }
    r->close();
    dir->close();
}
