com.browseengine.bobo.geosearch.solo.index.impl.GeoOnlyIndexerTest.java Source code

Java tutorial

Introduction

Here is the source code for com.browseengine.bobo.geosearch.solo.index.impl.GeoOnlyIndexerTest.java

Source

/**
 * This software is licensed to you under the Apache License, Version 2.0 (the
 * "Apache License").
 *
 * LinkedIn's contributions are made under the Apache License. If you contribute
 * to the Software, the contributions will be deemed to have been made under the
 * Apache License, unless you expressly indicate otherwise. Please do not make any
 * contributions that would be inconsistent with the Apache License.
 *
 * You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, this software
 * distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache
 * License for the specific language governing permissions and limitations for the
 * software governed under the Apache License.
 *
 *  2012 LinkedIn Corp. All Rights Reserved.  
 */

package com.browseengine.bobo.geosearch.solo.index.impl;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IndexOutput;
import org.apache.lucene.store.Lock;
import org.apache.lucene.store.LockFactory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.RAMDirectory;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.annotation.IfProfileValue;

import com.browseengine.bobo.geosearch.IGeoConverter;
import com.browseengine.bobo.geosearch.bo.GeoSearchConfig;
import com.browseengine.bobo.geosearch.impl.GeoConverter;
import com.browseengine.bobo.geosearch.index.bo.GeoCoordinate;
import com.browseengine.bobo.geosearch.index.bo.GeoCoordinateField;
import com.browseengine.bobo.geosearch.index.impl.GeoSegmentReader;
import com.browseengine.bobo.geosearch.index.impl.GeoSegmentWriter;
import com.browseengine.bobo.geosearch.solo.bo.IDGeoRecord;
import com.browseengine.bobo.geosearch.solo.bo.IndexTooLargeException;

/**
 * 
 * @author gcooney
 *
 */
@IfProfileValue(name = "test-suite", values = { "unit", "all" })
public class GeoOnlyIndexerTest {
    Mockery context = new Mockery() {
        {
            setImposteriser(ClassImposteriser.INSTANCE);
        }
    };

    int idByteLength = GeoSearchConfig.DEFAULT_ID_BYTE_COUNT;

    GeoOnlyIndexer indexer;
    private GeoSearchConfig config;
    private Directory directory;
    private String indexName;

    private GeoSegmentReader<IDGeoRecord> mockGeoSegmentReader;
    private GeoSegmentWriter<IDGeoRecord> mockGeoSegmentWriter;
    private IGeoConverter geoConverter;

    private Lock mockLock;

    Set<IDGeoRecord> lastDataFlushed = null;

    @SuppressWarnings("unchecked")
    @Before
    public void setUp() throws IOException {
        indexName = UUID.randomUUID().toString();
        config = new GeoSearchConfig();
        directory = new RAMDirectory();
        directory.setLockFactory(new LockFactory() {
            @Override
            public void clearLock(String arg0) throws IOException {
                //do nothing
            }

            @Override
            public Lock makeLock(String arg0) {
                return mockLock;
            }
        });

        mockGeoSegmentReader = context.mock(GeoSegmentReader.class);
        mockGeoSegmentWriter = context.mock(GeoSegmentWriter.class);
        mockLock = context.mock(Lock.class);

        expectLockObtain();
        geoConverter = new GeoConverter();

        indexer = new GeoOnlyIndexer(config, directory, indexName) {
            @Override
            GeoSegmentWriter<IDGeoRecord> getGeoSegmentWriter(Set<IDGeoRecord> dataToFlush) {
                lastDataFlushed = dataToFlush;
                return mockGeoSegmentWriter;
            }

            @Override
            GeoSegmentReader<IDGeoRecord> getGeoSegmentReader() {
                return mockGeoSegmentReader;
            }
        };

        context.assertIsSatisfied();
    }

    @After
    public void tearDown() {
        context.assertIsSatisfied();
    }

    private void expectLockObtain() throws IOException {
        context.checking(new Expectations() {
            {
                one(mockLock).obtain();
                will(returnValue(true));
            }
        });
    }

    @Test
    public void testIndex_and_flush_emptyIndex() throws IOException, IndexTooLargeException {
        int countEntries = 50;

        index_and_flush(countEntries, false, Collections.<IDGeoRecord>emptySet());
    }

    @Test
    public void testIndex_and_flush_existingIndex() throws IOException, IndexTooLargeException {
        int countEntries = 50;
        int idByteLength = GeoSearchConfig.DEFAULT_ID_BYTE_COUNT;

        Set<IDGeoRecord> existingData = new HashSet<IDGeoRecord>();
        for (int i = 0; i < 50; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);
            GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());

            existingData.add(
                    geoConverter.toIDGeoRecord(geoCoordinate.getLatitude(), geoCoordinate.getLongitude(), uuid));
        }

        createIndexFileDummyData();

        index_and_flush(countEntries, true, existingData);
    }

    @Test
    public void testIndex_and_flush_maxEntries() throws IOException, IndexTooLargeException {
        int maxEntries = 100;
        config.setMaxIndexSize(maxEntries);

        index_and_flush(maxEntries, false, Collections.<IDGeoRecord>emptySet());
    }

    @Test(expected = IndexTooLargeException.class)
    public void testIndex_and_flush_tooManyEntries() throws IOException, IndexTooLargeException {
        int maxEntries = 100;
        config.setMaxIndexSize(maxEntries);

        index_and_flush(maxEntries + 1, false, Collections.<IDGeoRecord>emptySet());
    }

    private void createIndexFileDummyData() throws IOException {
        IndexOutput output = directory.createOutput(indexName + ".geo");
        output.writeString("dummyData");
        output.flush();
    }

    private void index_and_flush(final int countEntries, final boolean fileExists,
            final Set<IDGeoRecord> existingData) throws IOException, IndexTooLargeException {
        int idByteLength = GeoSearchConfig.DEFAULT_ID_BYTE_COUNT;
        String fieldName = "field1";

        for (int i = 0; i < countEntries; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);
            GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());
            GeoCoordinateField field = new GeoCoordinateField(fieldName, geoCoordinate);

            indexer.index(uuid, field);
        }

        context.checking(new Expectations() {
            {
                if (fileExists) {
                    one(mockGeoSegmentReader).getIterator(with(IDGeoRecord.MIN_VALID_GEORECORD),
                            with(IDGeoRecord.MAX_VALID_GEORECORD));
                    will(returnValue(existingData.iterator()));

                    one(mockGeoSegmentReader).close();
                }

                if ((countEntries + existingData.size()) <= config.getMaxIndexSize()) {
                    one(mockGeoSegmentWriter).close();
                }

                one(mockLock).release();
            }
        });

        try {
            indexer.flush();
        } finally {
            indexer.close();
        }

        context.assertIsSatisfied();

        assertEquals(countEntries + existingData.size(), lastDataFlushed.size());
    }

    @Test
    public void test_delete_and_flush_emptyIndex() throws IOException, IndexTooLargeException {
        int countEntries = 10;

        Set<byte[]> toDelete = new HashSet<byte[]>();
        for (int i = 0; i < countEntries; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);
            toDelete.add(uuid);
        }
        delete_and_flush(toDelete, false, Collections.<IDGeoRecord>emptySet(), 0);
    }

    @Test
    public void test_delete_and_flush_existingIndex() throws IOException, IndexTooLargeException {
        int countToDelete = 10;

        createIndexFileDummyData();

        Set<IDGeoRecord> existingData = new HashSet<IDGeoRecord>();
        Set<byte[]> toDelete = new HashSet<byte[]>();
        for (int i = 0; i < 50; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);
            GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());

            existingData.add(
                    geoConverter.toIDGeoRecord(geoCoordinate.getLatitude(), geoCoordinate.getLongitude(), uuid));

            if (i < countToDelete) {
                toDelete.add(uuid);
            }
        }

        delete_and_flush(toDelete, true, existingData, 40);
    }

    @Test
    public void test_delete_and_flush_multipleRecords() throws IOException, IndexTooLargeException {
        int countToDelete = 5;

        createIndexFileDummyData();

        Set<IDGeoRecord> existingData = new HashSet<IDGeoRecord>();
        Set<byte[]> toDelete = new HashSet<byte[]>();
        for (int i = 0; i < 10; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);

            for (int j = 0; j < i + 1; j++) {
                GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());
                existingData.add(geoConverter.toIDGeoRecord(geoCoordinate.getLatitude(),
                        geoCoordinate.getLongitude(), uuid));
            }

            if (i < countToDelete) {
                toDelete.add(uuid);
            }
        }

        delete_and_flush(toDelete, true, existingData, 40);
    }

    private void delete_and_flush(Set<byte[]> toDelete, final boolean fileExists,
            final Set<IDGeoRecord> existingData, int expectedIndexSize) throws IOException, IndexTooLargeException {
        for (byte[] uuid : toDelete) {
            indexer.delete(Arrays.copyOf(uuid, uuid.length));
        }

        context.checking(new Expectations() {
            {
                if (fileExists) {
                    one(mockGeoSegmentReader).getIterator(with(IDGeoRecord.MIN_VALID_GEORECORD),
                            with(IDGeoRecord.MAX_VALID_GEORECORD));
                    will(returnValue(existingData.iterator()));

                    one(mockGeoSegmentReader).close();
                }

                one(mockGeoSegmentWriter).close();

                one(mockLock).release();
            }
        });

        indexer.flush();

        indexer.close();

        context.assertIsSatisfied();

        assertEquals(expectedIndexSize, lastDataFlushed.size());
    }

    @Test
    public void test_index_delete_and_flush_maxEntries() throws IOException, IndexTooLargeException {
        config.setMaxIndexSize(50);
        doTest_index_delete_and_flush();
    }

    @Test(expected = IndexTooLargeException.class)
    public void test_index_delete_and_flush_tooManyEntries() throws IOException, IndexTooLargeException {
        config.setMaxIndexSize(49);
        doTest_index_delete_and_flush();
    }

    @Test
    public void test_index_delete_and_flush() throws IOException, IndexTooLargeException {
        doTest_index_delete_and_flush();
    }

    private void doTest_index_delete_and_flush() throws IOException, IndexTooLargeException {
        final Set<IDGeoRecord> existingData = new HashSet<IDGeoRecord>();
        Set<byte[]> toDelete = new HashSet<byte[]>();
        Set<byte[]> toIndex = new HashSet<byte[]>();
        for (int i = 0; i < 10; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);

            for (int j = 0; j < i + 1; j++) {
                GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());
                existingData.add(geoConverter.toIDGeoRecord(geoCoordinate.getLatitude(),
                        geoCoordinate.getLongitude(), uuid));
            }

            if (i < 5) {
                toDelete.add(uuid);
            }

            toIndex.add(uuid);
        }

        for (byte[] uuid : toDelete) {
            indexer.delete(Arrays.copyOf(uuid, uuid.length));
        }

        for (byte[] uuid : toIndex) {
            GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());
            GeoCoordinateField field = new GeoCoordinateField("fieldname", geoCoordinate);

            indexer.index(Arrays.copyOf(uuid, uuid.length), field);
        }

        createIndexFileDummyData();

        context.checking(new Expectations() {
            {
                one(mockGeoSegmentReader).getIterator(with(IDGeoRecord.MIN_VALID_GEORECORD),
                        with(IDGeoRecord.MAX_VALID_GEORECORD));
                will(returnValue(existingData.iterator()));

                one(mockGeoSegmentReader).close();

                if (50 <= config.getMaxIndexSize()) {
                    one(mockGeoSegmentWriter).close();
                }

                one(mockLock).release();
            }
        });

        try {
            indexer.flush();
        } finally {
            indexer.close();
        }

        context.assertIsSatisfied();

        assertEquals(50, lastDataFlushed.size());
    }

    @Test(expected = IOException.class)
    public void index_and_flush_error_on_read() throws IOException, IndexTooLargeException {
        String fieldName = "field1";

        for (int i = 0; i < 10; i++) {
            byte[] uuid = generateRandomUUIDAsBytes(idByteLength);
            GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());
            GeoCoordinateField field = new GeoCoordinateField(fieldName, geoCoordinate);

            indexer.index(uuid, field);
        }

        createIndexFileDummyData();

        context.checking(new Expectations() {
            {
                one(mockGeoSegmentReader).getIterator(with(IDGeoRecord.MIN_VALID_GEORECORD),
                        with(IDGeoRecord.MAX_VALID_GEORECORD));
                will(throwException(new IOException()));

                one(mockGeoSegmentReader).close();
            }
        });

        indexer.flush();
    }

    @Test(expected = LockObtainFailedException.class)
    public void index_and_flush_lockObtainFailed() throws IOException {
        context.checking(new Expectations() {
            {
                one(mockLock).obtain();
                will(returnValue(false));
            }
        });

        GeoOnlyIndexer indexer = new GeoOnlyIndexer(config, directory, indexName);
    }

    @Test(expected = IllegalArgumentException.class)
    public void index_baduuid() throws IOException {
        String fieldName = "myField";
        GeoCoordinate geoCoordinate = new GeoCoordinate(randomLattitude(), randomLongitude());
        GeoCoordinateField field = new GeoCoordinateField(fieldName, geoCoordinate);

        indexer.index(new byte[] { (byte) 0 }, field);
    }

    private double randomLongitude() {
        return Math.random() * 360.0 - 180.0;
    }

    private double randomLattitude() {
        return Math.random() * 180.0 - 90.0;
    }

    private byte[] generateRandomUUIDAsBytes(int byteSize) {
        byte[] uuid = new byte[byteSize];
        for (int i = 0; i < byteSize; i++) {
            uuid[i] = (byte) ((Math.random() * (Byte.MAX_VALUE - Byte.MIN_VALUE)) + Byte.MIN_VALUE);
        }

        return uuid;
    }
}