/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.lucene.facet.taxonomy.directory; import java.io.IOException; import java.util.HashSet; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.taxonomy.FacetLabel; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.DiskOrdinalMap; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.MemoryOrdinalMap; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter.OrdinalMap; import org.apache.lucene.store.Directory; import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.TestUtil; public class TestAddTaxonomy extends FacetTestCase { private void dotest(int ncats, final int range) throws Exception { final AtomicInteger numCats = new AtomicInteger(ncats); Directory dirs[] = new Directory[2]; for (int i = 0; i < dirs.length; i++) { dirs[i] = newDirectory(); final DirectoryTaxonomyWriter tw = new DirectoryTaxonomyWriter(dirs[i]); Thread[] addThreads = new Thread[4]; for (int j = 0; j < addThreads.length; j++) { addThreads[j] = new Thread() { @Override public void run() { Random random = random(); while (numCats.decrementAndGet() > 0) { String cat = Integer.toString(random.nextInt(range)); try { tw.addCategory(new FacetLabel("a", cat)); } catch (IOException e) { throw new RuntimeException(e); } } } }; } for (Thread t : addThreads) t.start(); for (Thread t : addThreads) t.join(); tw.close(); } DirectoryTaxonomyWriter tw = new DirectoryTaxonomyWriter(dirs[0]); OrdinalMap map = randomOrdinalMap(); tw.addTaxonomy(dirs[1], map); tw.close(); validate(dirs[0], dirs[1], map); IOUtils.close(dirs); } private OrdinalMap randomOrdinalMap() throws IOException { if (random().nextBoolean()) { return new DiskOrdinalMap(createTempFile("taxoMap", "")); } else { return new MemoryOrdinalMap(); } } private void validate(Directory dest, Directory src, OrdinalMap ordMap) throws Exception { DirectoryTaxonomyReader destTR = new DirectoryTaxonomyReader(dest); try { final int destSize = destTR.getSize(); DirectoryTaxonomyReader srcTR = new DirectoryTaxonomyReader(src); try { int[] map = ordMap.getMap(); // validate taxo sizes int srcSize = srcTR.getSize(); assertTrue("destination taxonomy expected to be larger than source; dest=" + destSize + " src=" + srcSize, destSize >= srcSize); // validate that all source categories exist in destination, and their // ordinals are as expected. for (int j = 1; j < srcSize; j++) { FacetLabel cp = srcTR.getPath(j); int destOrdinal = destTR.getOrdinal(cp); assertTrue(cp + " not found in destination", destOrdinal > 0); assertEquals(destOrdinal, map[j]); } } finally { srcTR.close(); } } finally { destTR.close(); } } public void testAddEmpty() throws Exception { Directory dest = newDirectory(); DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest); destTW.addCategory(new FacetLabel("Author", "Rob Pike")); destTW.addCategory(new FacetLabel("Aardvarks", "Bob")); destTW.commit(); Directory src = newDirectory(); new DirectoryTaxonomyWriter(src).close(); // create an empty taxonomy OrdinalMap map = randomOrdinalMap(); destTW.addTaxonomy(src, map); destTW.close(); validate(dest, src, map); IOUtils.close(dest, src); } public void testAddToEmpty() throws Exception { Directory dest = newDirectory(); Directory src = newDirectory(); DirectoryTaxonomyWriter srcTW = new DirectoryTaxonomyWriter(src); srcTW.addCategory(new FacetLabel("Author", "Rob Pike")); srcTW.addCategory(new FacetLabel("Aardvarks", "Bob")); srcTW.close(); DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest); OrdinalMap map = randomOrdinalMap(); destTW.addTaxonomy(src, map); destTW.close(); validate(dest, src, map); IOUtils.close(dest, src); } // A more comprehensive and big random test. public void testBig() throws Exception { dotest(200, 10000); dotest(1000, 20000); dotest(400000, 1000000); } // a reasonable random test public void testMedium() throws Exception { Random random = random(); int numTests = atLeast(3); for (int i = 0; i < numTests; i++) { dotest(TestUtil.nextInt(random, 2, 100), TestUtil.nextInt(random, 100, 1000)); } } public void testSimple() throws Exception { Directory dest = newDirectory(); DirectoryTaxonomyWriter tw1 = new DirectoryTaxonomyWriter(dest); tw1.addCategory(new FacetLabel("Author", "Mark Twain")); tw1.addCategory(new FacetLabel("Animals", "Dog")); tw1.addCategory(new FacetLabel("Author", "Rob Pike")); Directory src = newDirectory(); DirectoryTaxonomyWriter tw2 = new DirectoryTaxonomyWriter(src); tw2.addCategory(new FacetLabel("Author", "Rob Pike")); tw2.addCategory(new FacetLabel("Aardvarks", "Bob")); tw2.close(); OrdinalMap map = randomOrdinalMap(); tw1.addTaxonomy(src, map); tw1.close(); validate(dest, src, map); IOUtils.close(dest, src); } public void testConcurrency() throws Exception { // tests that addTaxonomy and addCategory work in parallel final int numCategories = atLeast(10000); // build an input taxonomy index Directory src = newDirectory(); DirectoryTaxonomyWriter tw = new DirectoryTaxonomyWriter(src); for (int i = 0; i < numCategories; i++) { tw.addCategory(new FacetLabel("a", Integer.toString(i))); } tw.close(); // now add the taxonomy to an empty taxonomy, while adding the categories // again, in parallel -- in the end, no duplicate categories should exist. Directory dest = newDirectory(); final DirectoryTaxonomyWriter destTW = new DirectoryTaxonomyWriter(dest); Thread t = new Thread() { @Override public void run() { for (int i = 0; i < numCategories; i++) { try { destTW.addCategory(new FacetLabel("a", Integer.toString(i))); } catch (IOException e) { // shouldn't happen - if it does, let the test fail on uncaught exception. throw new RuntimeException(e); } } } }; t.start(); OrdinalMap map = new MemoryOrdinalMap(); destTW.addTaxonomy(src, map); t.join(); destTW.close(); // now validate DirectoryTaxonomyReader dtr = new DirectoryTaxonomyReader(dest); // +2 to account for the root category + "a" assertEquals(numCategories + 2, dtr.getSize()); HashSet<FacetLabel> categories = new HashSet<>(); for (int i = 1; i < dtr.getSize(); i++) { FacetLabel cat = dtr.getPath(i); assertTrue("category " + cat + " already existed", categories.add(cat)); } dtr.close(); IOUtils.close(src, dest); } }